diff --git a/.github/workflows/hqc-kem.yml b/.github/workflows/hqc-kem.yml
new file mode 100644
index 00000000..9a199555
--- /dev/null
+++ b/.github/workflows/hqc-kem.yml
@@ -0,0 +1,78 @@
+name: hqc-kem
+
+on:
+  pull_request:
+    paths:
+      - ".github/workflows/hqc-kem.yml"
+      - "hqc-kem/**"
+      - "Cargo.*"
+  push:
+    branches: master
+
+defaults:
+  run:
+    working-directory: hqc-kem
+
+env:
+  RUSTFLAGS: "-Dwarnings"
+  CARGO_INCREMENTAL: 0
+
+# Cancels CI jobs when new commits are pushed to a PR branch
+concurrency:
+  group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
+  cancel-in-progress: true
+
+jobs:
+  set-msrv:
+    uses: RustCrypto/actions/.github/workflows/set-msrv.yml@master
+    with:
+      msrv: 1.85.0
+
+  minimal-versions:
+    if: false
+    uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master
+    with:
+      working-directory: ${{ github.workflow }}
+
+  test:
+    needs: set-msrv
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        rust:
+          - ${{needs.set-msrv.outputs.msrv}}
+          - stable
+    steps:
+      - uses: actions/checkout@v6.0.2
+        with:
+          submodules: recursive
+      - uses: RustCrypto/actions/cargo-cache@master
+      - uses: dtolnay/rust-toolchain@master
+        with:
+          toolchain: ${{ matrix.rust }}
+      - run: cargo build --benches
+      - run: cargo build --benches --all-features
+      - run: cargo test --no-default-features
+      - run: cargo test
+      - run: cargo test --all-features
+
+  cross:
+    needs: set-msrv
+    strategy:
+      matrix:
+        include:
+          - target: powerpc-unknown-linux-gnu
+            rust: ${{needs.set-msrv.outputs.msrv}}
+          - target: powerpc-unknown-linux-gnu
+            rust: stable
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v6.0.2
+        with:
+          submodules: recursive
+      - uses: dtolnay/rust-toolchain@master
+        with:
+          toolchain: ${{ matrix.rust }}
+          targets: ${{ matrix.target }}
+      - uses: RustCrypto/actions/cross-install@master
+      - run: cross test --release --target ${{ matrix.target }} --all-features
diff --git a/Cargo.lock b/Cargo.lock
index ca3a4dd6..29ba717d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -73,6 +73,15 @@ version = "2.11.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
 
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
 [[package]]
 name = "block-buffer"
 version = "0.12.0"
@@ -102,9 +111,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
 
 [[package]]
 name = "cc"
-version = "1.2.61"
+version = "1.2.62"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
+checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98"
 dependencies = [
  "find-msvc-tools",
  "shlex",
@@ -161,8 +170,8 @@ version = "0.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e34d8227fe1ba289043aeb13792056ff80fd6de1a9f49137a5f499de8e8c78ea"
 dependencies = [
- "block-buffer",
- "crypto-common",
+ "block-buffer 0.12.0",
+ "crypto-common 0.2.1",
  "inout",
 ]
 
@@ -321,6 +330,16 @@ dependencies = [
  "zeroize",
 ]
 
+[[package]]
+name = "crypto-common"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
 [[package]]
 name = "crypto-common"
 version = "0.2.1"
@@ -398,15 +417,25 @@ dependencies = [
  "zeroize",
 ]
 
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer 0.10.4",
+ "crypto-common 0.1.7",
+]
+
 [[package]]
 name = "digest"
 version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
 dependencies = [
- "block-buffer",
+ "block-buffer 0.12.0",
  "const-oid",
- "crypto-common",
+ "crypto-common 0.2.1",
  "ctutils",
 ]
 
@@ -424,8 +453,8 @@ checksum = "cda94f31325c4275e9706adecbb6f0650dee2f904c915a98e3d81adaaaa757aa"
 dependencies = [
  "base16ct",
  "crypto-bigint",
- "crypto-common",
- "digest",
+ "crypto-common 0.2.1",
+ "digest 0.11.3",
  "hkdf",
  "hybrid-array",
  "rand_core",
@@ -492,7 +521,7 @@ dependencies = [
  "serde_json",
  "serde_yaml",
  "serdect",
- "sha3",
+ "sha3 0.11.0",
  "subtle",
  "thiserror",
  "toml",
@@ -541,6 +570,16 @@ dependencies = [
  "slab",
 ]
 
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
 [[package]]
 name = "getrandom"
 version = "0.4.2"
@@ -598,9 +637,9 @@ dependencies = [
 
 [[package]]
 name = "hashbrown"
-version = "0.17.0"
+version = "0.17.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
+checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
 
 [[package]]
 name = "heapless"
@@ -652,7 +691,27 @@ version = "0.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f"
 dependencies = [
- "digest",
+ "digest 0.11.3",
+]
+
+[[package]]
+name = "hqc-kem"
+version = "0.1.0"
+dependencies = [
+ "const-oid",
+ "criterion",
+ "hex",
+ "hybrid-array",
+ "kem",
+ "pkcs8",
+ "rand",
+ "serde",
+ "serdect",
+ "sha3 0.10.9",
+ "subtle",
+ "thiserror",
+ "typenum",
+ "zeroize",
 ]
 
 [[package]]
@@ -680,7 +739,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
 dependencies = [
  "equivalent",
- "hashbrown 0.17.0",
+ "hashbrown 0.17.1",
  "serde",
  "serde_core",
 ]
@@ -711,9 +770,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
 
 [[package]]
 name = "js-sys"
-version = "0.3.97"
+version = "0.3.98"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf"
+checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08"
 dependencies = [
  "cfg-if",
  "futures-util",
@@ -731,6 +790,15 @@ dependencies = [
  "elliptic-curve",
 ]
 
+[[package]]
+name = "keccak"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653"
+dependencies = [
+ "cpufeatures 0.2.17",
+]
+
 [[package]]
 name = "keccak"
 version = "0.2.0"
@@ -747,7 +815,7 @@ version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "01737161ba802849cfd486b5bd209d38ba4943494c249a8126005170c7621edd"
 dependencies = [
- "crypto-common",
+ "crypto-common 0.2.1",
  "rand_core",
 ]
 
@@ -801,7 +869,7 @@ dependencies = [
  "rand_core",
  "serde",
  "serde_json",
- "sha3",
+ "sha3 0.11.0",
  "zeroize",
 ]
 
@@ -1003,7 +1071,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1b52e6ee42db392378a95622b463c9740631171d1efce43fa445a569c1600cb6"
 dependencies = [
  "crypto-bigint",
- "crypto-common",
+ "crypto-common 0.2.1",
  "rand_core",
  "rustcrypto-ff",
  "subtle",
@@ -1052,6 +1120,17 @@ version = "6.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
 
+[[package]]
+name = "rand"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207"
+dependencies = [
+ "chacha20",
+ "getrandom",
+ "rand_core",
+]
+
 [[package]]
 name = "rand_core"
 version = "0.10.1"
@@ -1321,7 +1400,17 @@ checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
 dependencies = [
  "cfg-if",
  "cpufeatures 0.3.0",
- "digest",
+ "digest 0.11.3",
+]
+
+[[package]]
+name = "sha3"
+version = "0.10.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874"
+dependencies = [
+ "digest 0.10.7",
+ "keccak 0.1.6",
 ]
 
 [[package]]
@@ -1330,8 +1419,8 @@ version = "0.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "be176f1a57ce4e3d31c1a166222d9768de5954f811601fb7ca06fc8203905ce1"
 dependencies = [
- "digest",
- "keccak",
+ "digest 0.11.3",
+ "keccak 0.2.0",
 ]
 
 [[package]]
@@ -1499,6 +1588,12 @@ version = "0.2.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
 
+[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
 [[package]]
 name = "walkdir"
 version = "2.5.0"
@@ -1529,9 +1624,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen"
-version = "0.2.120"
+version = "0.2.121"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1"
+checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790"
 dependencies = [
  "cfg-if",
  "once_cell",
@@ -1542,9 +1637,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.120"
+version = "0.2.121"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103"
+checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578"
 dependencies = [
  "quote",
  "wasm-bindgen-macro-support",
@@ -1552,9 +1647,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.120"
+version = "0.2.121"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41"
+checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2"
 dependencies = [
  "bumpalo",
  "proc-macro2",
@@ -1565,9 +1660,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.120"
+version = "0.2.121"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea"
+checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441"
 dependencies = [
  "unicode-ident",
 ]
@@ -1608,9 +1703,9 @@ dependencies = [
 
 [[package]]
 name = "web-sys"
-version = "0.3.97"
+version = "0.3.98"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2eadbac71025cd7b0834f20d1fe8472e8495821b4e9801eb0a60bd1f19827602"
+checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa"
 dependencies = [
  "js-sys",
  "wasm-bindgen",
@@ -1754,7 +1849,7 @@ dependencies = [
  "rand_core",
  "serde",
  "serde_json",
- "sha3",
+ "sha3 0.11.0",
  "x25519-dalek",
  "zeroize",
 ]
@@ -1795,6 +1890,20 @@ name = "zeroize"
 version = "1.8.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
+dependencies = [
+ "zeroize_derive",
+]
+
+[[package]]
+name = "zeroize_derive"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
 
 [[package]]
 name = "zmij"
diff --git a/Cargo.toml b/Cargo.toml
index a5dedb5b..bc134788 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -4,6 +4,7 @@ members = [
     "dhkem",
     "frodo-kem",
     "ml-kem",
+    "hqc-kem",
     "module-lattice",
     "x-wing"
 ]
diff --git a/hqc-kem/Cargo.toml b/hqc-kem/Cargo.toml
new file mode 100644
index 00000000..17759474
--- /dev/null
+++ b/hqc-kem/Cargo.toml
@@ -0,0 +1,61 @@
+[package]
+name = "hqc-kem"
+categories = ["cryptography"]
+description = "Pure Rust implementation of HQC-KEM as described in FIPS 207"
+edition = "2024"
+homepage = "https://github.com/RustCrypto/KEMs/tree/master/hqc-kem"
+keywords = ["hqc", "kem", "post-quantum", "cryptography", "codes"]
+license = "MIT OR Apache-2.0"
+readme = "README.md"
+repository = "https://github.com/RustCrypto/KEMs"
+version = "0.1.0"
+
+[features]
+default = ["kgen", "ecap", "dcap"]
+kgen = []
+ecap = []
+dcap = []
+kem = ["dep:kem_traits", "dep:hybrid-array", "dep:typenum", "kgen", "ecap", "dcap"]
+pkcs8 = ["dep:const-oid", "dep:pkcs8"]
+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 }
+pkcs8 = { version = "0.11", optional = true, default-features = false }
+rand = "0.10"
+sha3 = "0.10"
+serdect = { version = "0.4", optional = true }
+serde = { version = "1", features = ["derive"], optional = true }
+subtle = "2"
+thiserror = "2"
+typenum = { version = "1", optional = true }
+zeroize = { version = "1", features = ["derive"] }
+
+[dev-dependencies]
+criterion = "0.7"
+hex = "0.4"
+rand = { version = "0.10", features = ["thread_rng"] }
+
+[[bench]]
+name = "kem"
+harness = false
+
+[lints.rust]
+missing_docs = "deny"
+missing_debug_implementations = "deny"
+unused_import_braces = "deny"
+unused_lifetimes = "deny"
+unused_parens = "deny"
+unused_qualifications = "deny"
+unused_results = "deny"
+unused_extern_crates = "deny"
+
+[lints.clippy]
+unwrap_used = "deny"
+panic = "warn"
+panic_in_result_fn = "warn"
diff --git a/hqc-kem/README.md b/hqc-kem/README.md
new file mode 100644
index 00000000..ca258fbe
--- /dev/null
+++ b/hqc-kem/README.md
@@ -0,0 +1,209 @@
+# hqc-kem
+
+Pure Rust implementation of **HQC-KEM** (Hamming Quasi-Cyclic Key Encapsulation Mechanism), a post-quantum KEM based on quasi-cyclic codes over the ring Z_2[X]/(X^n-1).
+
+HQC uses concatenated Reed-Solomon + Reed-Muller error correction with the Fujisaki-Okamoto transform for IND-CCA2 security. It's currently selected as the backup approved KEM to ML-KEM.
+
+[![Crates.io](https://img.shields.io/crates/v/hqc-kem.svg)](https://crates.io/crates/hqc-kem)
+[![Documentation](https://docs.rs/hqc-kem/badge.svg)](https://docs.rs/hqc-kem)
+[![License](https://img.shields.io/crates/l/hqc-kem.svg)](https://github.com/mikelodder7/hqc-kem)
+
+## References
+
+- [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
+
+## Security Levels
+
+| Level | Type Alias | NIST Category | Public Key | Secret Key | Ciphertext | Shared Secret |
+|-------|-----------|---------------|------------|------------|------------|---------------|
+| HQC-128 | `Hqc128` | Level 1 (128-bit) | 2,241 B | 2,321 B | 4,433 B | 32 B |
+| HQC-192 | `Hqc192` | Level 3 (192-bit) | 4,514 B | 4,602 B | 8,978 B | 32 B |
+| HQC-256 | `Hqc256` | Level 5 (256-bit) | 7,237 B | 7,333 B | 14,421 B | 32 B |
+
+### Key Generation
+
+```rust
+use hqc_kem::{Hqc256, HqcKem};
+
+let mut rng = rand::rng();
+let (ek, dk) = Hqc256::generate_key(&mut rng);
+
+// Access raw bytes
+let pk_bytes: &[u8] = ek.as_ref();
+let sk_bytes: &[u8] = dk.as_ref();
+```
+
+### Encapsulation
+
+```rust
+use hqc_kem::{Hqc256, HqcKem};
+
+let mut rng = rand::rng();
+let (ek, dk) = Hqc256::generate_key(&mut rng);
+
+// Sender encapsulates with the public key
+let (ct, shared_secret) = ek.encapsulate(&mut rng);
+
+let ct_bytes: &[u8] = ct.as_ref();
+let ss_bytes: &[u8] = shared_secret.as_ref();
+```
+
+### Decapsulation
+
+```rust
+use hqc_kem::{Hqc256, HqcKem};
+
+let mut rng = rand::rng();
+let (ek, dk) = Hqc256::generate_key(&mut rng);
+let (ct, ss_sender) = ek.encapsulate(&mut rng);
+
+// Receiver decapsulates with the secret key
+let ss_receiver = dk.decapsulate(&ct);
+
+assert_eq!(ss_sender, ss_receiver);
+```
+
+### Serialization / Deserialization
+
+All types implement `AsRef<[u8]>` and `TryFrom<&[u8]>` for raw byte conversion:
+
+```rust
+use hqc_kem::{Hqc128, HqcKem, EncapsulationKey, Hqc128Params};
+
+let mut rng = rand::rng();
+let (ek, dk) = Hqc128::generate_key(&mut rng);
+
+// Serialize to bytes
+let pk_bytes: Vec<u8> = ek.as_ref().to_vec();
+
+// Deserialize from bytes
+let ek_restored: EncapsulationKey<Hqc128Params> = pk_bytes.as_slice().try_into()
+    .expect("invalid public key length");
+```
+
+With the `serde` feature enabled, all types implement `Serialize` and `Deserialize`:
+
+```toml
+[dependencies]
+hqc-kem = { version = "0.1", features = ["serde"] }
+```
+
+```rust,ignore
+use hqc_kem::{Hqc128, HqcKem};
+
+let mut rng = rand::rng();
+let (ek, _dk) = Hqc128::generate_key(&mut rng);
+
+// Serialize to JSON (hex-encoded)
+let json = serde_json::to_string(&ek).unwrap();
+
+// Deserialize from JSON
+let ek_restored: hqc_kem::EncapsulationKey<hqc_kem::Hqc128Params> =
+    serde_json::from_str(&json).unwrap();
+```
+
+### Deterministic Key Generation
+
+Generate identical key pairs from a 32-byte seed:
+
+```rust
+use hqc_kem::{Hqc128, HqcKem};
+
+let seed = [0x42u8; 32];
+let (ek, dk) = Hqc128::generate_key_deterministic(&seed);
+
+// Same seed always produces the same key pair
+let (ek2, dk2) = Hqc128::generate_key_deterministic(&seed);
+assert_eq!(ek.as_ref(), ek2.as_ref());
+```
+
+### Deterministic Encapsulation
+
+Produce identical ciphertext and shared secret from a message and salt:
+
+```rust
+use hqc_kem::{Hqc128, HqcKem, hqc128};
+
+let mut rng = rand::rng();
+let (ek, dk) = Hqc128::generate_key(&mut rng);
+
+// Message size depends on security level (16/24/32 bytes)
+let m = [0xABu8; hqc128::MESSAGE_SIZE];
+let salt = [0xCDu8; hqc128::SALT_SIZE];
+
+let (ct, ss) = ek.encapsulate_deterministic(&m, &salt).unwrap();
+
+// Same inputs always produce the same output
+let (ct2, ss2) = ek.encapsulate_deterministic(&m, &salt).unwrap();
+assert_eq!(ct.as_ref(), ct2.as_ref());
+assert_eq!(ss, ss2);
+
+// Decapsulation works as usual
+let ss3 = dk.decapsulate(&ct);
+assert_eq!(ss, ss3);
+```
+
+Message sizes per security level:
+
+| Level | `MESSAGE_SIZE` | `SALT_SIZE` |
+|-------|---------------|-------------|
+| HQC-128 | 16 bytes | 16 bytes |
+| HQC-192 | 24 bytes | 16 bytes |
+| HQC-256 | 32 bytes | 16 bytes |
+
+### Module-Style API
+
+For a more concise import style, use the security-level modules directly:
+
+```rust
+use hqc_kem::hqc128;
+
+let mut rng = rand::rng();
+let (ek, dk) = hqc128::generate_key(&mut rng);
+let (ct, ss1) = ek.encapsulate(&mut rng);
+let ss2 = dk.decapsulate(&ct);
+assert_eq!(ss1, ss2);
+```
+
+### Generic Code
+
+Write code that works across all security levels:
+
+```rust,ignore
+use hqc_kem::{HqcKem, HqcParams, EncapsulationKey, DecapsulationKey};
+
+fn roundtrip<P: HqcParams>(rng: &mut impl rand::CryptoRng) {
+    let (ek, dk) = HqcKem::<P>::generate_key(rng);
+    let (ct, ss1) = ek.encapsulate(rng);
+    let ss2 = dk.decapsulate(&ct);
+    assert_eq!(ss1, ss2);
+}
+```
+
+## Features
+
+| Feature | Default | Description |
+|---------|---------|-------------|
+| `kgen` | Yes | Key generation (`HqcKem::generate_key`) |
+| `ecap` | Yes | Encapsulation (`EncapsulationKey::encapsulate`) |
+| `dcap` | Yes | Decapsulation (`DecapsulationKey::decapsulate`) |
+| `serde` | No | Serde `Serialize`/`Deserialize` for all types |
+
+## Security
+
+- Constant-time operations for side-channel resistance (via `subtle` crate)
+- Secret key material is zeroized on drop (via `zeroize` crate)
+- Shared secrets use constant-time equality comparison
+- IND-CCA2 security via Fujisaki-Okamoto transform with implicit rejection
+
+## License
+
+Licensed under either of:
+
+- [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)
+- [MIT License](http://opensource.org/licenses/MIT)
+
+at your option.
diff --git a/hqc-kem/benches/kem.rs b/hqc-kem/benches/kem.rs
new file mode 100644
index 00000000..21ac9a04
--- /dev/null
+++ b/hqc-kem/benches/kem.rs
@@ -0,0 +1,41 @@
+#![allow(missing_docs, unused_results)]
+#![cfg(all(feature = "kgen", feature = "ecap", feature = "dcap"))]
+
+use criterion::{Criterion, criterion_group, criterion_main};
+use hqc_kem::{hqc128, hqc192, hqc256};
+
+fn bench_keygen(c: &mut Criterion) {
+    let mut rng = rand::rng();
+
+    c.bench_function("keygen_128", |b| b.iter(|| hqc128::generate_key(&mut rng)));
+    c.bench_function("keygen_192", |b| b.iter(|| hqc192::generate_key(&mut rng)));
+    c.bench_function("keygen_256", |b| b.iter(|| hqc256::generate_key(&mut rng)));
+}
+
+fn bench_encaps(c: &mut Criterion) {
+    let mut rng = rand::rng();
+    let (ek128, _) = hqc128::generate_key(&mut rng);
+    let (ek192, _) = hqc192::generate_key(&mut rng);
+    let (ek256, _) = hqc256::generate_key(&mut rng);
+
+    c.bench_function("encaps_128", |b| b.iter(|| ek128.encapsulate(&mut rng)));
+    c.bench_function("encaps_192", |b| b.iter(|| ek192.encapsulate(&mut rng)));
+    c.bench_function("encaps_256", |b| b.iter(|| ek256.encapsulate(&mut rng)));
+}
+
+fn bench_decaps(c: &mut Criterion) {
+    let mut rng = rand::rng();
+    let (ek128, dk128) = hqc128::generate_key(&mut rng);
+    let (ek192, dk192) = hqc192::generate_key(&mut rng);
+    let (ek256, dk256) = hqc256::generate_key(&mut rng);
+    let (ct128, _) = ek128.encapsulate(&mut rng);
+    let (ct192, _) = ek192.encapsulate(&mut rng);
+    let (ct256, _) = ek256.encapsulate(&mut rng);
+
+    c.bench_function("decaps_128", |b| b.iter(|| dk128.decapsulate(&ct128)));
+    c.bench_function("decaps_192", |b| b.iter(|| dk192.decapsulate(&ct192)));
+    c.bench_function("decaps_256", |b| b.iter(|| dk256.decapsulate(&ct256)));
+}
+
+criterion_group!(benches, bench_keygen, bench_encaps, bench_decaps);
+criterion_main!(benches);
diff --git a/hqc-kem/kat/hqc-1.rsp b/hqc-kem/kat/hqc-1.rsp
new file mode 100644
index 00000000..176f8ab4
--- /dev/null
+++ b/hqc-kem/kat/hqc-1.rsp
@@ -0,0 +1,9 @@
+# HQC-1
+
+count = 0
+seed = 9EF877FDDBE8891C6E4E79EAF022E563DEFACA6B152161B9A423E8FE96A403E774B2D352CF74C934069C9DE74757F505
+pk = 4053237912EA281C51C4456A5096589EC9D20219651E00F9704178F0CF84F9AEBF6F02DD0EBAA1059A6E8944703770A74496DAF9E0B255E97A16B577C61B8FB99F3D11A8DB672C05AF484B70C354C56518AE0D0B093C0811F1134BBFD8358847F18051749AE26476DD2C304A4CA3FB5621D4CAB0407673819B4433AA0BBA0B3F9DFF1572B8EF8F0938CC83991246ACE5862584537C9F89BC93311CE076F91FDDB95AC619B8DF5F5DD415667BE349716FF91EAAABE6C828BFDF52E403F5C99FAA55C36FC8F3E2ACCCC39474C0EE8FF33E0EE55A19C14BEB031E14855CB4A56010A7A42486A042738D0FDF358C951F703C488B19AA52A30B894C176D639D5D7459727B9BB59BA73A88E560382D4A6FC55424E08A0DB8F88F9BF5809EC94C06258DBDFA25CC0419052603C446E33D9310EF040498B8C683C399C7569BBF265F7BBD587D8551DCC62EC1F147E996B73D867374F4FEE2036C637F68B9B81BBE8A69CB19F6FCA579FD1687EF949CB4B20771DEEB8451CCDD68BA97CE55DCECBE1B09D215265E77A73254B5ED2BAE89D05708AE98E170664D9F79D593FA01008AC65A8D74661337D6C1244A274A755877D4FE5CB38BB8F1A0863263099FF235B082AEE55A107EA54B06385F56662632EDD825109A55A08799E5E3210E862FEBF294F1DA84265887B8D068CC961E28D12B0860D9634D8CD285884FBB81B693B202592527E6E6A68FC012F06947AE85656D3E664140EFBDA95B0DDE925B02D8EE38653EC448D88199970E35C5CFC6D962EF7CB883C248505AA5CE7FEED89F7866CD00099428C7EA12B0B8493F21804AD44E6808F427E0931CE7F33C35D3990334C354835210319C5E1824AF471D6F7376143C5BC81F543D38909B3A5CB790CAF94CD48F00574EC44AECDB710D3E6BEF83AF8151B6CD669D520609D705C6597AE7D646D4EDABDB0A0E959EAFBFBB3E4186D0BFA41439D926189EA355E986C60F2DB8B83FC086820552ED756C63BB7B958CE401EF84C7D6E6404403A56D07D7BB74D727D603A4DC3442CAA79160F5FBDF3228BA401AC23E5E32B675540E195C1107CD76C5EB8962B1C5D574E8B5967E24D34B135AACF5CE1DF6EAA6CADC9EF561C521684B83E5B0DDBC355091C1C15952ADB88F0267A7904E0C7108071DCD1FD94317DE55AEBD9D85EC7A2F78A00C7F76F07164AEA4FE60332D5950D7838FED0E35A5D1146B4A92A2BC3B9CD8FE3F85070A85BCF8561229A55FB18BF0D2C0F40E3C8A1EA08F2A14D4B7204908D1330543660562188D25E6F8098556C152F5CB9E0D5F8D4F93FE7B2F9946AFF7847B9E49C78F6D1E83FEAFAE9323EB03E483AD52983F538A5B01A7B9D23A6178FC5288F8DDB61E7B284A55974BD71A05560AE8D72F2341E28A7009D2A27205524F44F7DBAD2C55BED9E8B9D54B9C0B48BD4BD9E1E18C5EC3BB1BD67E8AC4C3F7B61E7205112E1A8183AFB760BFD66603BB54CF9E8747CA990701A9581A25A798653F3AC2BC8B0958DCB3DEEB92AEB0844B1D1F00E9A85CF9E9C127B47FDB57B132DBF1086675307DCD6EE2C18C3E48B1509AB1E9B4F64741753D50A4EA6CA15ACF1CAF57A736C364894F8144133679EE7C285408F0DFA1DD6482F592A196E45AFA07A4557D28DB85C6F030E8FDD8353B65D50269B4C7F2E7F4C96344146453651CFE2BD144AE05672FEE5466804BDEC9438B518C2F8B8169A116886BD04365F37790775DC465E584F1E98C9CC85950041B5C658E771A8D40DE18A24F4B1FC1FDC402FDC9B91420A6A506CA9A452A9CC7C157B175E549D5BAA55C9614E9E2A05CB4215610E674F3FD5DABB65BAC2514EE120CB0C5AAB1616DE54B17863F312B48C826C05A54E41E2428794FB0288F24DCAA27C445044A2BD3757F017FB5A261078670DFE3F81647FD39DDACE082B0FBD772BBC2327C7480076465D715A0A6192784B14A30EE965914E3658BE0C72CC9DCAE96D13BB8AF0D55D59A58704B59860ADB8741C24A54980FC62362FAB74673F61DA346D01A852F43AE5C9A6AC908814910AD3542C76B2A5FB20C68287CC7BC9F87BAFC43D6CA9B6E3D90CD681FD18E73E5691A18AF2ADFF34F794F4EA2C48D6E9C315F62125C1D7C74C21B37A717EEEDA109F7F1D75E9C4F98E597EA61AE373D5C678BA6D258FB89A3E10AAD04DD931B97B8CC33A36959C67DCBD94549CAD38DF43DE03444399F8FEC5AF2C59ECA0603550A9627AD74C0F2EB5D52C5DE5FAF3737E512A7D81A51B9E683B33D091E5C970BA90CA0C6351E4A268DC30622B0C130AB67B2A156026BA6F0817CD134555895BD7F632C611DE487E9451DC9D64ED61558BB2294EC0C400CEDC9B502D0D959568CD7F3F07116ABE29B62FB4DC0347093B5B4473A4BBBD74776BCE46283C0C2ECD13E60318E6AB7EFF76E0F64CDAD56D53A0B1E7F88B57497B834E5837C49EC45F1916CAB67F9C3686DF77A974A0E88E29F352FF13D74F3A9AF7749DFE0AA4E7A089B096C162F9AA84A1183C1526FFA62EB3722CFE1FDD93AFE5DEF7CE5E12F37FF1C929484E7E25B23B22A47633D801272B8A8E6CFB9F63971E665A679E622F494C83463F45CC80C51303C008357ADAF12618A9964E6C12C6A13CB0530FBF03B030F82FB1D1D23913266B69E5305D199BCB32676875CA1287E1EAC976AA4148362BCF9CBE5BC6CD1AB5277A1FF1172D893317CE4050FE8D5830BBFD598767C5488665419600D4423ECF5A7C99CD6F1EA9D1FDDFCD49B6D9FB78C4EAB73CEE27755E66E0A529CDE6423B4C13F5160DE8639AC25BCE3F9A4AA2CCB5F6774CA6775636FC07BAEE42B18871CBBD25EFF40106C9CB66B39D226516E2AEC92FD1D840870E2DB8615FB5B7EDB8EB81F79907AD51320E06840943FDE537FA2FA756A40CB13C30439F0C98BE795C835280BDD29A32EBBBC1C03D54EFA161EC4C3ECAD5CC269C3C1CC5C85E8F60D49D3B22ACDA6E95E22A4573CA1095E4B59595E496A11AEF6484A54549B1C709697E2AAAF4EC5048B126EBBA1DA27E5AC4EC4AA04B2F25C11458FCF442E602270FEE03AF19335B24583B9595DDD9A6BEB642A92D85D61224619A198F500565F3F4D702E6AD3E93A107A9DCC0BA762322A6D578E2128C6418E8733B1167F94001E212A26FF940A5299D31BE7AA1BB9681807
+sk = 4053237912EA281C51C4456A5096589EC9D20219651E00F9704178F0CF84F9AEBF6F02DD0EBAA1059A6E8944703770A74496DAF9E0B255E97A16B577C61B8FB99F3D11A8DB672C05AF484B70C354C56518AE0D0B093C0811F1134BBFD8358847F18051749AE26476DD2C304A4CA3FB5621D4CAB0407673819B4433AA0BBA0B3F9DFF1572B8EF8F0938CC83991246ACE5862584537C9F89BC93311CE076F91FDDB95AC619B8DF5F5DD415667BE349716FF91EAAABE6C828BFDF52E403F5C99FAA55C36FC8F3E2ACCCC39474C0EE8FF33E0EE55A19C14BEB031E14855CB4A56010A7A42486A042738D0FDF358C951F703C488B19AA52A30B894C176D639D5D7459727B9BB59BA73A88E560382D4A6FC55424E08A0DB8F88F9BF5809EC94C06258DBDFA25CC0419052603C446E33D9310EF040498B8C683C399C7569BBF265F7BBD587D8551DCC62EC1F147E996B73D867374F4FEE2036C637F68B9B81BBE8A69CB19F6FCA579FD1687EF949CB4B20771DEEB8451CCDD68BA97CE55DCECBE1B09D215265E77A73254B5ED2BAE89D05708AE98E170664D9F79D593FA01008AC65A8D74661337D6C1244A274A755877D4FE5CB38BB8F1A0863263099FF235B082AEE55A107EA54B06385F56662632EDD825109A55A08799E5E3210E862FEBF294F1DA84265887B8D068CC961E28D12B0860D9634D8CD285884FBB81B693B202592527E6E6A68FC012F06947AE85656D3E664140EFBDA95B0DDE925B02D8EE38653EC448D88199970E35C5CFC6D962EF7CB883C248505AA5CE7FEED89F7866CD00099428C7EA12B0B8493F21804AD44E6808F427E0931CE7F33C35D3990334C354835210319C5E1824AF471D6F7376143C5BC81F543D38909B3A5CB790CAF94CD48F00574EC44AECDB710D3E6BEF83AF8151B6CD669D520609D705C6597AE7D646D4EDABDB0A0E959EAFBFBB3E4186D0BFA41439D926189EA355E986C60F2DB8B83FC086820552ED756C63BB7B958CE401EF84C7D6E6404403A56D07D7BB74D727D603A4DC3442CAA79160F5FBDF3228BA401AC23E5E32B675540E195C1107CD76C5EB8962B1C5D574E8B5967E24D34B135AACF5CE1DF6EAA6CADC9EF561C521684B83E5B0DDBC355091C1C15952ADB88F0267A7904E0C7108071DCD1FD94317DE55AEBD9D85EC7A2F78A00C7F76F07164AEA4FE60332D5950D7838FED0E35A5D1146B4A92A2BC3B9CD8FE3F85070A85BCF8561229A55FB18BF0D2C0F40E3C8A1EA08F2A14D4B7204908D1330543660562188D25E6F8098556C152F5CB9E0D5F8D4F93FE7B2F9946AFF7847B9E49C78F6D1E83FEAFAE9323EB03E483AD52983F538A5B01A7B9D23A6178FC5288F8DDB61E7B284A55974BD71A05560AE8D72F2341E28A7009D2A27205524F44F7DBAD2C55BED9E8B9D54B9C0B48BD4BD9E1E18C5EC3BB1BD67E8AC4C3F7B61E7205112E1A8183AFB760BFD66603BB54CF9E8747CA990701A9581A25A798653F3AC2BC8B0958DCB3DEEB92AEB0844B1D1F00E9A85CF9E9C127B47FDB57B132DBF1086675307DCD6EE2C18C3E48B1509AB1E9B4F64741753D50A4EA6CA15ACF1CAF57A736C364894F8144133679EE7C285408F0DFA1DD6482F592A196E45AFA07A4557D28DB85C6F030E8FDD8353B65D50269B4C7F2E7F4C96344146453651CFE2BD144AE05672FEE5466804BDEC9438B518C2F8B8169A116886BD04365F37790775DC465E584F1E98C9CC85950041B5C658E771A8D40DE18A24F4B1FC1FDC402FDC9B91420A6A506CA9A452A9CC7C157B175E549D5BAA55C9614E9E2A05CB4215610E674F3FD5DABB65BAC2514EE120CB0C5AAB1616DE54B17863F312B48C826C05A54E41E2428794FB0288F24DCAA27C445044A2BD3757F017FB5A261078670DFE3F81647FD39DDACE082B0FBD772BBC2327C7480076465D715A0A6192784B14A30EE965914E3658BE0C72CC9DCAE96D13BB8AF0D55D59A58704B59860ADB8741C24A54980FC62362FAB74673F61DA346D01A852F43AE5C9A6AC908814910AD3542C76B2A5FB20C68287CC7BC9F87BAFC43D6CA9B6E3D90CD681FD18E73E5691A18AF2ADFF34F794F4EA2C48D6E9C315F62125C1D7C74C21B37A717EEEDA109F7F1D75E9C4F98E597EA61AE373D5C678BA6D258FB89A3E10AAD04DD931B97B8CC33A36959C67DCBD94549CAD38DF43DE03444399F8FEC5AF2C59ECA0603550A9627AD74C0F2EB5D52C5DE5FAF3737E512A7D81A51B9E683B33D091E5C970BA90CA0C6351E4A268DC30622B0C130AB67B2A156026BA6F0817CD134555895BD7F632C611DE487E9451DC9D64ED61558BB2294EC0C400CEDC9B502D0D959568CD7F3F07116ABE29B62FB4DC0347093B5B4473A4BBBD74776BCE46283C0C2ECD13E60318E6AB7EFF76E0F64CDAD56D53A0B1E7F88B57497B834E5837C49EC45F1916CAB67F9C3686DF77A974A0E88E29F352FF13D74F3A9AF7749DFE0AA4E7A089B096C162F9AA84A1183C1526FFA62EB3722CFE1FDD93AFE5DEF7CE5E12F37FF1C929484E7E25B23B22A47633D801272B8A8E6CFB9F63971E665A679E622F494C83463F45CC80C51303C008357ADAF12618A9964E6C12C6A13CB0530FBF03B030F82FB1D1D23913266B69E5305D199BCB32676875CA1287E1EAC976AA4148362BCF9CBE5BC6CD1AB5277A1FF1172D893317CE4050FE8D5830BBFD598767C5488665419600D4423ECF5A7C99CD6F1EA9D1FDDFCD49B6D9FB78C4EAB73CEE27755E66E0A529CDE6423B4C13F5160DE8639AC25BCE3F9A4AA2CCB5F6774CA6775636FC07BAEE42B18871CBBD25EFF40106C9CB66B39D226516E2AEC92FD1D840870E2DB8615FB5B7EDB8EB81F79907AD51320E06840943FDE537FA2FA756A40CB13C30439F0C98BE795C835280BDD29A32EBBBC1C03D54EFA161EC4C3ECAD5CC269C3C1CC5C85E8F60D49D3B22ACDA6E95E22A4573CA1095E4B59595E496A11AEF6484A54549B1C709697E2AAAF4EC5048B126EBBA1DA27E5AC4EC4AA04B2F25C11458FCF442E602270FEE03AF19335B24583B9595DDD9A6BEB642A92D85D61224619A198F500565F3F4D702E6AD3E93A107A9DCC0BA762322A6D578E2128C6418E8733B1167F94001E212A26FF940A5299D31BE7AA1BB9681807374B10C73F79FA08D0731BE4F21356D191782EB1D10DEEA5929523B3B4D6D97BF397572E7CEAC24CD55009F822EBE800CEFC0D60050E04C3171859E54BA888D2F670E22EBE926B0B307A65264FBC08F8
+ct = B14D1AEEB2A88F27BFED21FF0E6C60F378663DED9C8B69A55488D0D140061E766EABAB01C583058B7A4468C13B4265560323B98192A492CF09B6B1DF57158E259B5BB7A813BEFDDE33B4AD6C90B430EFC74FFF2077A1CF2E4166F5BBCD56A516A5494458EEE1DF02FBF9AE2EB0D3BEB1FE86E4C97C281703F50A0113BD760C6EEEE362673A2629807710AB153125E4912F695C91B9137715AE9F6F036626D025FF60D1647424B20340BDFC5595AE5089594098BE4D1C028EBE9C55472FA395C7AE9E603CE09A470DA4BDE7496A49FBF1B6FCE5A37723D06E7A249E24051BE6E6AF8DFC018504110C45B2781A8E40D1B485605D7A0F01660107DBDABA620172F55898BCCD85FF420667CBBA901862E7C2D3F5288379D25DDC58F45E0DEC4610332CCAC9D1D06F32B6C73F94A314D4DBAF66AE9D66B5E3D3D7507BD3FD976502BEA5AF9F2DE26A69FF49DE36DB1495CD83EA3D74C436CDF9CB29E73BDECE670923FB8BC2EB762DA17EC2429B415A57DF0CD085F19E8B97A210102C766904D4CF99800EDB62EB416D2538309E14EB8913E4C72D68F27A8DAC93BFE1119E82BD7599EFD7FD7CA1F0FB2D24F7C46C60B244CE3E8941CEAAA6A0D39CA4F7A493F722ECB9BA1DC26783B858CA0BD4807C2A335AC14D2801B42351B9068BD9F216F21F7840F4D3E9E13A4FD2FD8DA0E67CA44CCA3A6839BAC423FEA0E8B1886677B5CA20B45DA3AD36F19E064714E4FE39CDFC1C4AC7AD8598ECA0ABBD4F8EB69BA125EFC2800978E11796A0C8F6A88F4D22ED1C59111F00C41EF11E9DD7E2DB892875148BF29D2DFBA92D93A16E07E721E1B02726799C09094EB43B2168B60AD630595E23BEF8197258187D5FA623AD18E1F482FFC30BB5707C4EAA266FB2156BCD034E340FB735AC0DDC60B7FC8F699338AC7D8954189582A7113854EE23062F05FA287941E71E66CCED457BE9B5FC36E9EE313A84F4750823D2354F71227FBD7CF746822F7FD4F580BA03CE49DE34BF70AD7BA08EB4898E8BC188F11D2918BEC122AC085C2CD2420242FAB9571F2B27F69B1291D6920CA1D455486FB1EB00896217AD6C5DAF33688B6EFF0D9DB3A2C7EFF1422AFE1F54C90DED4855F4933C0E8B8D9F022D50FB2720680C4E28A8DB4FCC8174CED5B2CCB5E608137DE71BADD62B90B576A12474DD9C9A7CF03CD47985B5CB45E18B09CE3C2D0D7A33F50A364E07339B2DD45E278B5C65FDAC41EF812A860A77939345EC148CF0CB54CD89E81C1A864584CDCD6B1BEE2C0B4D94E80680DE23F77FFC96F1EED5F7739B5ECF517EE64DC3314A1229A043835DF3E2B9F450D15BA860E6236560785C562D0F04D8C7BE5C5FC3BE2434486F213571C64BA069AD20D0C1D6C324E0746F6BFACC55CB529A5829CAE1100F48C21C86E045C30FBF4E1110599834111ABBF399E0EBB75B7B239DA19129EFCD1900A3B6F73491CFA9CEF71612AACA153073AA68A266C05F516759D64DD2AC8DB9CE2F3E4432662A298BD61714ECFB3981230462B82E5B45A37F28801ED06B52A73ADB616BBFE327FF1ED5FD5478EDC2990A1F494F4D3F241E0D3A2E4754CC2A321C51FAC80D2BE986FEFA4AA420047E8455165F3D03E49ED843C74C673C06C0FB036FD05372FD8EEC281AB0064CDE4A50B412E76F0737D608343FE7D7B80F4C5750D1BF1D97B5D981E21A0E233F7465AC0996554F74705B8BD3A7E19D3724892409056A1E20A8B0CA95D8C2E8D33D27900C2D476C5CBE9BBBF97FED0F87373FB7ACD81C4472DF6B869D7BBC56B39B840196B6EB03FA54B41BC64F47545A10327DF4DB89C3D4C46817F4920C96A8014C2F4AACD5D585A8A5F23A050FBA67DAA65AC80F63BED4DBE516C9DCDF99436BC60B25C2D39D4D3E302A42E4E308C1ECE1EB5181EF308BD1D04C0338FEC5CA54B20651AC9A8D9E01ED205699B0F038357C130E8143CBBC4A5D897BC347EE502DB881F2B41EA8C8B6C6E981BB6ED2D53C31FCAF062F5253E7A8B7325555DAB237DA8EAAE98A77938B77BC1F6C7F34CB1A228CA5CD07F2B785B5C2F6E40271CBDDF0755D6D16FFF8D515592F21B7B607F27882C844F328D3F46A7986E1495BBB383EAADC9B9298F2E01A9CB3B2047C10F3847F6E60E5CABBA37E09E3E39151FCCEA2A1393EDF12E284C855AB6B03844A352FA4F868EBB5F154B895302961ACB85CCA922608138F5242F36FEF0CD9FD0BF587C9ADC51A79DA89EBB803E9FF5214ABCE3B2DFB90942B432CA90B45EAA06915AF246B84A36A753D2DF6D26BD523580F1BA521DC0283AADEC31460B9C700248A8E731758243B1039B00E90FCF7137E1C6C4B8A1C1BF7374E267B9627010A10A4F7799CE56D7C846D0DEA0C54C119B8DEFBFF9C9F86A9C5D5E1F082E7A3C51C63E405CF4E376B60FB1FCA850ECAF4FE7184C50D94ED3E2ABABCBC1762D92E6F28CF2B0439DD1C1E5E492DFA88049B5F6F6108AD28F59F0E896340DA9E741FE223B81C1CC37BD53798D8F38E6BE469226732669C0C5B86C653505DC5051FE332B1D4EF72DEDA64154F34BC0FF2D8939BED418ADF323326D97BB8ACE6637E072890363F7BDCAACCB88B67280632E5FCA8DD5738D1A13351DEDD80BC59C3A953489AE4A891F6D26DE522BA54D4BC80DC379C042F5C583DE2BB2B477953CDD7A8EA89F7E8DD7F25CC319FF98117272C894DB6F72CED2BF7DB98D42BEDAD6A82A0D59DF702A7D8BE829B9F452218F6FFEDD902FD8D903C6BADCDCEF931D05B807E4623DCB02E2E97E16A02232FEC1CBF2443F13BB0F9162355D4A290AD677377A8DBA6A763C7448CAB87042EC1E6CD4B5209836D3EDF43919EE63231364A9655F2BBA0C272D1D1D19528EC330551DA74E57023985F8827098EBFE4A402DD75E5B8880E1AE6511CE2B9B9CA63F780CC8BC897E9AFBB0E44173827B0540E1AF007CF0DBE4D013786AB6397BB2F31334BE82352EA6E2A8C34EEE61C6700B16ED5E79336937EA6513CFFCA778BA194BA4E04DB49A516DE965FD2FE2568AF1F646201D5E0B395D732979F7BA6025CBAA9D4C10E7EDF56D20B119DBB7D3F8C82B546A00C83C9E66A320BCE37C08E49D0F47E9EAEEA037099905D8021EB0665B77B4F2D15BF14CE5A69C0667F31E3E4D7DEF49E3F478EAD3E059A16817B879A8FD64C081734B8C2A2755F407525DB0BACB1C989C6B806A2D979FBFA7902B03358AFAA70A33A54F3E5E8BB8A4346B0FB823CBD777CC65A73BFCF5A6799E157D9456668D378A97B090EE8063273B61AE1063ECD994FC6E5123F3BC1E013329E2E9C0403A08B35651A01352041EC596E072521EC7AD66CDC3B5ACEF02B2AF574618D4A41454D57337380E785ECD0C29A81D3C788EDB2021D31C0DA098741EFAA7834EFDC17BA704CA0217F65515B3FFAD1C326BB631147DA0EB5E8FA5ED739332CD4ADD8C209CC2C307E293AFD0ED4AE045912B154712CE703AA71980CE955EAFB9CDF105047ACCC96B256E81ACC6183F3651357C6F1037FEFC7992C0A0014D3B9A96F56E60F6AFFD0CAE4653F03D7BDDA35AB3E1AA8C5CCF1CDFD79457C4BF02262724C0F54A0F241FBB4AEA2E09D897694E50C0F67892A0EACFC6EA3FF9A6AB49AD6691DA620A4E49607D426E613329418EBFC0E325CE7516034EAEDEBBDD5D79327B395E4AB30CE3AA6FC707F528D7BF0D4CC972CD8B126FE3D499DB151E770DB7597BFB4AF658D3AC7BDCDC718DFF61F2D8E313AB592AD0180591D37AB135728FCAD836BB64B9089360436575AEF9C81B4C1F33BB80ABAB33F37CF4829E1FDDE8C42114BAE0BD5F0C96BB910C08E4AE423EB524EA714A6121E1F8176414096143975CA3B66BA6DEF555623414ED5A61C92175492070AABAE9230AD3569BF804DDC923CB2EE3E833B4B660E2D03E04D57AA92E2634082EA6865269B4BEF26A3045E29FCBF64349112E3FC52F0274AA466B851D142EF07A2A944A68805C1ABEBC214AA52CEB6BEB8C79477B1D50F0F4291337858416DE4F1ABB8ADB25418A23BB905DA64741FC7B7CB0E93933536D0E3FD351979FD1AE329C6F8B7BEB4C19202CA4B4EFC5E3B5C5C548C3972A6BF3BC714A1834662CDB43DB0B2D4F4CFF4A015AF422B4C3D304E1C1CA40AA65B13E795D4D30CB835E0D69232F97C59C57349A3EDBD2036A2E1642C571AB968D0191C4DBE6571CF003C66BF536B578C8840FD6887B38D2162DCC4F54FA3361C80BE4A546BDD94BD3958BA336A4D363D2B150C14F80948999AAFE651C3F4CA33B1ED689B121540B15E2D405C5C84AF68CAC084B0EED471B650FD7E5CFEFECDF01C796051D017ADEB5CAA6346FB15A5ECB8DA7565766456F8055248F1162D4EC866CE77677DFC58BCE8BE0A9A3F1AA948EB96D451FDBA5CC8D18F76C8A835BBA81AA6A6BE2851C65F0976C2D63E4532C2869AB171DE49341B36D973E0ADA34BBB733847331D138470E04E146535565D368C1D682FA5EB71F89AFAD5428E45D44EDB21C190089701106F0BF543B0E2D2B393FD57FF10E0ABEA665FD6BB8467C623FF57D7EFA0FC46F27AEFD5ECA19EBAB7E755AF26A666694827F0B8E4DCD3026A433697C96B46E01F591C8386E56495441830AEA1A7E855EB767A64F786C40539E5467E6AF6AF2095789382939F6B1984B62B62AB793C72564B935DAD7AD5B4B586D81F94639AFE742BE6091B19525C615A88E5223977A5CCBA652F5E9597988148F1A5E61FE3564F29CDAB14248FE3D91AF2AEF31FDCC2578016C7802D9D49F4561505FA9893483E077383D588624E764AB65ACB2F23247111A2FE294693AC0BFEEE913A28A56698BA8BD46353413B748EC53AE98FF995D6F676C8133DA2DD6188ABFAA8D8D81C945B1CE9ADB0320EEDF4B37432455C9C4D61D23985A903A8181939B042E3DF97764707F58F2C371FB0ED59A83D09CD1BEE88F02153F08DFDA6BA021E39F70BBFFB5E53110A753B74F7D40EF47071B6868A88DA0646C7B2FB1441B539C3AA8F43283D3A334C4C490683532B4D35B5D49CC80E265B6CBD1672F85B895539EB575546D06F2B763FCE4C89F7CCD2DACBEDC54E054286C919A6AB495F36E93FB6FA0F0E1EBBA3E38FC05A0426C88BCA22F942E345037510BEED4ECE7EFA9B9D2324B23C6BC8BD3E1D4C93FB8B612AE987D6D3971428375F545DD2567F79A7766B4909D762D3219B9151431F9899BD3D79C99A20530D99051A97438413F79F1FA74DD5A7B9865652DDEBAA04CD1BE40F32BA33B0D5ABAEE8C8D10E8E790A4F5D10606A104AF531AD494FB8CA234BE15463589A9C9D6DAAFF67394B62637954EB20D789D9734CEC924FFC713F62FBD234FF880D39E9F436E915F59E53473925408139AFA5CEE9815717DCA9A5141A3F020EDB9B5E4C5283E281024669BE396B8EB24E0B448F3E8440E4EC8A345913257A5631C64C78F00286E64212AAEC1E72997ADCCA88EB2492EDFBAB3FA83071A53D8D710186B262333A78C7CC2071509FAF3979A4477D0893D97D362965E67CEE4002E3035307D9BF9D2756F5B3CB8E92A9A54CA92CE93EFDBE107F92F125EEDC997C7F54F42D59D4E99A48D2873597A5A64B79A76E05EEC8815A38A8BE2BB63327D236D62A1A4090EDE6CDD26A2BC3743E81A46D344487284AEC95A6906A57CFAB88DE3B8C13842FC2145941B38810A83828B554DAD6669AD9776E97D32E443B14BB70087CBAA36B6301985B5BF868F13BCED348D906EC403409D1049C817918589BC19824E58999507F4B8F10E5953E23DFC48F61EF9FFC7E45CF9284CB4BAACE825405580974F099A8AA576B2CA9D6E4FA2E5EAE053716AF6EA71B5B57C873BF891DF5D3B843EB4685FDFC010B06C8D004C11BB5CA6CE70139C48FD2FCE2CE651340DC6ECA4685121676544C137A5CDAC718D69C88EDA376DD49605664339068B08FD445E5A96201390A4F268306EA049CE28251195F4E2F430745C22810CE3C0F0F4BE63ABFCF89B49829FFC7D52CA2C7FD45DC303B80C2EA6E83684A0BDE84BE62C1415B3BBF4FF37E2BD17DD0CB487373AFAF9B6B6EA16EDD44AC7A50F84B7FC5A4D98C316C3E4B67BA6E2FCD618AE51CD5E22552028BA8AE3848469B2BC875774BDF8C3EAA4470A9DF63DFBA37B39D827792B2BD47BC5A98CF462D73FF2906EFC3E37E29DB8EFEBDE5683D794568060AB7D5E19739868CC58BDB4E6C63D1C820AE33AEFA611EBD6E8560F76DB9E7157B2DCFE56AFE8C397CF9F5BDD0038DCB605116E74BB80684D826860C7515CE86E35571F5
+ss = 31D476B2A4D41B493246E055FB9D3088B3D3E4AE8D480477C66A271920C6C849
+
diff --git a/hqc-kem/kat/hqc-3.rsp b/hqc-kem/kat/hqc-3.rsp
new file mode 100644
index 00000000..411fd893
--- /dev/null
+++ b/hqc-kem/kat/hqc-3.rsp
@@ -0,0 +1,9 @@
+# HQC-3
+
+count = 0
+seed = 9EF877FDDBE8891C6E4E79EAF022E563DEFACA6B152161B9A423E8FE96A403E774B2D352CF74C934069C9DE74757F505
+pk = 4053237912EA281C51C4456A5096589EC9D20219651E00F9704178F0CF84F9AEF3B1021BFEC31794714E6A9292AA08DD2487BE83C502127FB6218AFBA4E047C67EE8C5674181E692C9E5E79D5C6CD284618F911BD4A7B0D5BCD8C5A8DD64A4C588ED5570B88BA780149CD3F4AF573B90D7E683B8BEAAFF363FFF9F62911FDC2A8BD7E8265DB6079B9B36176DB31AB61744D6B9F1782063C5C6D1A14FAE23175AF6B11F82EB8C77DA0F9548432B62181C0C7C4CDA0C1C0E2EBDE07DFC5CF37CA9233101E5D66274E58F66E46F877BF3D84DD7328DD4FB87CEC269EA521E85122833BEFEE53F5E9F695424AB3108EA73C70B158777FA06C7AF2C6A42AE5BF0B73D602C95FE703D359F370DA17103358863134B8A730406F0077ABB2758A8A240C3545D564A0A2677A9A187FA6DCAEA1F25C77FD7D1353FAC494B4B1A03E433DA73F740562E0CC73C0838D525B8D1FCA73F4314ED02EB1F52770BB04FB32924488DCFB1A53A6D5949873DAA585C5371D3ED4B385DC28C5B2D2C1B0A6C1944E5AEA55C03A3CA10B78D5464D6D73F96E4FE6BF9C1EDFFF779A6F1435799B46A4C9B120FAC8F66FB3E5994AA02C2CC2572FEA373EAA5C4A81BBADAB2947731EE11E88834DDFB60F9C79BE6500C48FB3D49188DD02187BB37576BDB7400A1AB52FD3542DC36ADD213EDDDC69BB4CE5D7BB13F2BFB454AAA01C9A5EB9CC20FB4FF2745BF1D4EBFD3D0FD0FB5797F0C8940F2E8C2DC032C9CE8536F4EB785D2971F16C6B89B5BB5156C7083FB7695F9C76D3C08BB3E63A87D5D3CAEF1D1022F3D50489EF8FA2209C7EC1C1AA7ED6C34B04988E955AFDDBA5652D3347FEE8F6BE6991CE6870759F844E547977528AC837B67962682707B01AD2176F7965992AE98CFA1409753FEEA2B8EB9D99B96B2E8BDD598C088B53957D49FEBC8A7EFD54B0098D87430069570BC92F46BAEBF6A955F85ABF4085FCBDB0D24345DB579684CF74FE002A72C2F21A29114EC682CA4A06274DE884A92E417D1F2DFB23C11EC40C824A65D75E447857361942FAFFB82CDAA82856A3A09187AEEDEC6C686E3B132E25ADE8E9DA2FEEBDA93465838D1E6AA231D63F3368FFAC03D1F1E8C3AD2D16853A48C103C4408544EC719412A9345CEF0282D25FECE2C1FD158DE045D63E1FC8D4B1E7079F1379398F762C2924E51841E7F826D703DB2E645622CFF600C6FCD31F32A3943D5C8D34086FF84A6D7BACB902BEA94358C3DCEFB378F9188585F85DFF08D764BCE7D0F64C9364E394EA6976A9A7BC2EE949CC650965CC2321FFF2BB1BB77085E3242C90B1BEE992C10B4A41BEA5A8AB5A948412A6F5BCFDE95DF3E733BE325870E39CE1A069613448B12905CBD30D9ADBD5B36FDC82933C9652D647E3AB1E32AF89E9BEEB52CD2A719FF1FD31996E3A0EF89827CE3E59DFDF47E97BE4AA957FB2D4494D616D1E2453FCB5D840B28945F2AD82C3DB197194E7115E10CBFCD27C61B6A99BDFDEA390BED262677C8FF7271CC3C98C6F1833C8FF2E937617CB33E84D7197738E1B1BB4ECBFD734AF7C66BA51BB963E45D9BF796C810AD62D07A28149584A7CF37C718A0CA02EF2A425C30FBD475A133DD9AC34EE08D150CB9B094809C9FCD90C688D0FD749DBB87B8F0E5410537C64821909DB40C7592195E8E7FAD956596118CB902EC808B910928F2E8382BDC7E0BBE5FA617250E37EF82920DE4F9EEFB79731A23F3E9FE84482566F51CB6F49F8FEA8826182D03061717A4EC58561696B6AB06A284378338FF29CADC6A0655BA3E0FFEA739A6C8D8A4B72B55B2A99AAD8D628BCD28D10A53A54A6A30272991DBF109335B96ECEB4301A5584F57F35D5A470592FB20708C6686F40AF6FF9622AD87DFBED69B7FEA33D169E8A8837DC5AB33D30424C9CF2917ABC4458AA442A7B43F2586E567C1351E4A3488C62105EEBC93E0373D3F8B1F5CA2B704411058DC40F9C33E41B32B700D29099F99F98AE5B693976B39C1ADF2BAE1AE67047162D1337B6B4EE5111A9A3CE7AE1FA0B4F57553468B400AF5E37C1D1A27C2774F15F699905FF17A04A126C1C26090FBF695EBCE0CB9850F8647E9F1E156950BC6537061B70ED0BD7D9FED822F55757E8779645BE4AF8FC2EF8BE62575A3F86C596FEBA80134B9396ED5F4692C39606647F06FCFDD65B585320C9313E35C88BEBA32115889271E615D06299680692898908540B4BF840CD744A47BE305BF252F41BEF23CBEA46871F59858E806A0F1251B1E3253A0B7D9790BF5D76B5A16678C723B1CAF5F48B0B706164D02696A70C4019D34FC7E72A8BE73C0C025FD43FB2CBFA417FF759DF3EC8523069B528BD0409102761F37222FD485933B3EF898756FC3D82AF7592DBDADB3269F7F6EDECC9AB34A84DD50B0BE923618A442B4248C51B1640C5369FC162DF4238F8CD5E4D9A80055C9B498CD3B1B6CE8BB32DCA38D1AEC63A27150D8737CDB1E69126250F4179358D66875F8AB2A6659047917BDFEC6E0EA68725D56CEF9D0AB43B927D32F15E3418AD30AD02ABCB6FEB060C2F8FF4023A3A80F17816E2534619B72F9C8FC513375F66BC873408163040944625631309A3330D56C971E9C12A6FC56544C155363AC3BA72A4D64DEB72B0D380C5F66C5047425D2239B982406100BD7A020555DB54CB67F99E62C69927D4B908B4646D5B8975802988944EBBBED2566D07ADDCB6E01DBED97BAE32E86F6477C710423BD29EADF991649BA0A11BD793B7D91ED3D8B8A049A58762F8AE35A5C41A5D8F99876341B3B04E0CA40D11D71ABADE17690D3AE9A450181529C0DE6CF065B27C291CF72EA1F01280B81AE6C4F8DE7950D376E4A2556700914A18889B9D82BF83A7AC940288F48463F238AB67957BD9725C497A5AE7EA045E8E8CFFDA186CADC3B6F563C12695A5432BBDD905E2A61909F459352B9F542D85EEC2D1BE513E824BF8D5AB7D2C2C0B3A4C75586511D41A17BD09F5419E453D8DBB427FC1DA2B9B0B5361F72E885B022D787182D873899628446C6B1D16D49CE6527C13C21625E4561FB3491620A2594E904150FEC295FE1D74F711E74420A7015B9C8C557C0093083D15659475A2C1CAF99E8F150BA4E10F052AD153251E653C89B14B956294B428BCC7E465AAB050FF01D8D18B3078D2E683BB8CEC1AFC643426D85B12ED4E918E3C2189E4A739605D17EA9820CB02F8436F46E8FFE53F9ECD67F0E18C575D35ADC1DC08973113A32CF10E7872332F9E9EB3F147AAD59F93905302C3FF5A7432AC1DAE30A00ECF0E0A8BBDF775CC624E3C3DF99A74893BAC2522EA489F3C84323345D67699FB4FB721518581184B88C2DE7B46B0CFD7CAD7A6757C2F0D3A183CA67EC22E2AD19DD2E70C3F6EC7ACBAE1D3ADA166F766095318B5214BB1D1F56C853D18F4D3CCFA7D8690306E8A59D0BF3CAD0D85CC3335046011E14AB8B2C63636A5D2122633BA489FDAC878ABAB3DA3E8A4A3FC1EA0AC2FD7E788F54949BCDCFED4700BF361641BC37656887CD2F57C79075A9C775DCEFF4E30AF4195C7CE113E45BFC8F5A202ACB6FD44F2CCB2A9B780CC590BB3B36BD209E6CD306D4743F734347B041D1F716F6A4DB28F7346D27FC552EDA3096DC3E794444387F21B24DE6B2F417275BB1F5910803DC84AABA8D61E17C8FD676BE65A9D6E71FE245D88D48075CBAB15047EAA8E95D45140CCD870E3C4AFE32C13B99F5CCEA912350552259973F606B23C181CD61452CEE2EAD662E0FFA8865458B6279DD415091DEEF00347BFA624C400F4AB771948AE0F09845338BA5B643DC043C6DB014005AC6ACC149C08CDD8097928D5095FE6281AB042345BAF715750EE5B0CAE67E3D3EEEF82C032C07AD670FE6E04FE71315B698D17070DA615BA17F6349157B01CA7CBA4294CACA566AF8547FE1FBD6E066A6279D84D0A4A88533F3046CDAE52FE8AA8C81E61A1E614C4876460B6BE3DDF436F61E760F5233EC63B814111025037856462648866D608212B9F68EFC7341F8054E70202F833FE90DEA991269E9523B75566F46AA790AA412586AF6CABE4E8D53B17CED49D9D0116E73541BFE26F5B3B84292B6A6EBC46E8737092E760D640EFA09E0AD81537C1712F894196B30565C9439D8B78B36F0EB445A1C99FDA3C187EDDA2359D31D868700C2E0B9F2453DF691369009E5BCB14A95D89A3D7856D920417161F3DC3CAEFC80DF8F485492E615616284F4667ED60073D360B98963E4A83B145118D5C6308BC78F6582B3E258475AD1E5BEA67059C6BFFCDAED047829B5FC319112B9F243ECC363CE85053C445FADB5DE88318DAD27068253B2176B069B2985247BB6DC4D5D4FF550461741B92388142B6CF549B664160793D493BBB9E4AA729CF6721EC88F1B3D768DD6DBA755EFA3EBFDFAA55586257636EE767BE600127C1345502B24931D9933D1604A37AC0A1A437A6508708C9084B91047B2EC263E38629E32A1F8DDFEE588178257EFD2ADAB40C3264EDD4200BCADB8A13F2C68B9E8F6C84764BADF05AAF32CAD2E4E347F83D41B66080AB2AF01F76E512831CC08978568C7D2D75206E256194123F4C4981C6BB3BA9575937316969D2825C052515FAA7519A186FC9564C78F912B1B9B730AF07936585E64630A409DB1E370328063A3251F65771EECDFC087623D848C0B4C5A008719697425C4C0F13EB421E3770BA741AFB080B47C264DEE07F8891AB549A52C15B9D4D35E07A0FBC1F20BA6BF9F8B60E9D26F2F33973BA26530AEA8CD8CA259A1212D0A1E7E7675E8A792B4A3CCE5A5F0420BEB962D8B8D1D93CAFFFFC4BB8A72674CBD39F2B58A31F69FABC9ED3BF7EB8744E195E5FA02BF2F83B6EE5E4061AE86290FAF77443A175B9934846B09879B011448E75D248C338B6670C4B38F3566DE632002E1D8C09652C0BBCA8F0BACBC6E3E08AB001C06647CBCCCD2ED47FC92A48B4276E516794FDFB9F676EB2040E15E4DE78D8628FA2A8E89BB2CA517954D47C99800075A3C443BC511D061678CF7F06EEDE0FB7934CBA04893486E7CBC748E10C1E8B8ECEDE0520159DF7D0CD06D4750EBDC9105312D100A57D83C4D59A2225B31E6B006D90A554F452A74F4F01EC29983D428D5187468A5A58B54E128DA463201934D7EFB26F75C2446370E24AF9BD0B3029E72583F2A6A6C8946616B1966FC8C52C543EC480EB1FC6758502B1A0622BE55516CEFB71E0E19DC6983E03FE771D5DC0FCC91E06A4A70AE67FAF58878BC23CFB546692A11CFCD113D8702B9FE6B15B19D5D0844721184C99C84145EE2901CCA5419A03D0C552EA0FB871E6AFF64EEC57E969E9896D39824095E2CCE2D9CE0FC7F3F6E4CB5448CBB0958E4D63BF8BD8F25070BB6852887D827458747F245DC86B2E22F972683507E81993317F08757436FE3EE5280F79CEC1A99F9B2CEC7F7AD4AD742023882E869B546E4613C3CC33E08F62B89CCD60C99EA1ACBDDEE68A0E39C9271A52802B35B04D807889150180B08F593FEB90DEEC0EB99D49E2D8CF9059E8366A6D44A3D76A4EEC5D6328524ABD82BA11E8F850FA127B838C46A4EFA37227EF6D070F0D52F3123B1469E5EDE466EA7F0D80932BDB66F30E275D49406544616528EF68C30E3591A9BEE3B4ADCA7C7EA17938118654BB77EAA79677D9A881F0A50449D06B1BB0DEB3392ED451271BDC9618363779A5A1A14F8345F7A4F1EAC1ED9A59B0C7F602E408624341A12D4DBF7CFF403E72D653E67D959E63F14C022AB450B1817C2B7013C72A307D12EAF0391B8EAFF728239918FD46DF78696902FB31B8D66E53E11407FB2AE55A8F1406CFE3F27E41B0020527BB18010E4378F7EDA482423DACC496606FDBD949CF8474D243343C4CEA2A89A6ACD9CFC249D7C4C938B20C97264DE63EF93B0FE9EEC90E3994B424F39E739022FBE103E10E9687E663CA2AD1EA04198E6A78AC13B34EF69988373F2C1C378259F567FE6CEF739892B8B37CC0ED905A58F4F39763DC6BEB85A917AE99A3EA1BFF39793F2C176EE4C872354F61DD6E98F0C28E2094AB4259B617E4807BDA1A76FF9FFD2B33ED7BF8A15850A9D02A181D05036297C3F0BAA45DB973E2359F965A9FE0FA18997FEEEF34EA9F7EE701077992F2B41E87122DD784E23E2CCA5A8FA5F880B6E192D61E79F8FF3E5C20F2AA71A9696B1660093118DBDDB7D413D0002AD16BB49AE1B13AF8E05C643315D7A174B44D38A0FDEBB87B6D04F66848B08187A8C0735A4C5C29F9D75729250A8B856E57BEA623FBDD098FE2E185EA0343CBD105EB7C62BBDE70F28D3260D6343DD1C73D87E3E4ED3B85DD585707E4C08838FAA1D317A840269123189E4B607133364AD7EAB7427CC14E51AC0AB1A7C883A48AE7128093247615807
+sk = 4053237912EA281C51C4456A5096589EC9D20219651E00F9704178F0CF84F9AEF3B1021BFEC31794714E6A9292AA08DD2487BE83C502127FB6218AFBA4E047C67EE8C5674181E692C9E5E79D5C6CD284618F911BD4A7B0D5BCD8C5A8DD64A4C588ED5570B88BA780149CD3F4AF573B90D7E683B8BEAAFF363FFF9F62911FDC2A8BD7E8265DB6079B9B36176DB31AB61744D6B9F1782063C5C6D1A14FAE23175AF6B11F82EB8C77DA0F9548432B62181C0C7C4CDA0C1C0E2EBDE07DFC5CF37CA9233101E5D66274E58F66E46F877BF3D84DD7328DD4FB87CEC269EA521E85122833BEFEE53F5E9F695424AB3108EA73C70B158777FA06C7AF2C6A42AE5BF0B73D602C95FE703D359F370DA17103358863134B8A730406F0077ABB2758A8A240C3545D564A0A2677A9A187FA6DCAEA1F25C77FD7D1353FAC494B4B1A03E433DA73F740562E0CC73C0838D525B8D1FCA73F4314ED02EB1F52770BB04FB32924488DCFB1A53A6D5949873DAA585C5371D3ED4B385DC28C5B2D2C1B0A6C1944E5AEA55C03A3CA10B78D5464D6D73F96E4FE6BF9C1EDFFF779A6F1435799B46A4C9B120FAC8F66FB3E5994AA02C2CC2572FEA373EAA5C4A81BBADAB2947731EE11E88834DDFB60F9C79BE6500C48FB3D49188DD02187BB37576BDB7400A1AB52FD3542DC36ADD213EDDDC69BB4CE5D7BB13F2BFB454AAA01C9A5EB9CC20FB4FF2745BF1D4EBFD3D0FD0FB5797F0C8940F2E8C2DC032C9CE8536F4EB785D2971F16C6B89B5BB5156C7083FB7695F9C76D3C08BB3E63A87D5D3CAEF1D1022F3D50489EF8FA2209C7EC1C1AA7ED6C34B04988E955AFDDBA5652D3347FEE8F6BE6991CE6870759F844E547977528AC837B67962682707B01AD2176F7965992AE98CFA1409753FEEA2B8EB9D99B96B2E8BDD598C088B53957D49FEBC8A7EFD54B0098D87430069570BC92F46BAEBF6A955F85ABF4085FCBDB0D24345DB579684CF74FE002A72C2F21A29114EC682CA4A06274DE884A92E417D1F2DFB23C11EC40C824A65D75E447857361942FAFFB82CDAA82856A3A09187AEEDEC6C686E3B132E25ADE8E9DA2FEEBDA93465838D1E6AA231D63F3368FFAC03D1F1E8C3AD2D16853A48C103C4408544EC719412A9345CEF0282D25FECE2C1FD158DE045D63E1FC8D4B1E7079F1379398F762C2924E51841E7F826D703DB2E645622CFF600C6FCD31F32A3943D5C8D34086FF84A6D7BACB902BEA94358C3DCEFB378F9188585F85DFF08D764BCE7D0F64C9364E394EA6976A9A7BC2EE949CC650965CC2321FFF2BB1BB77085E3242C90B1BEE992C10B4A41BEA5A8AB5A948412A6F5BCFDE95DF3E733BE325870E39CE1A069613448B12905CBD30D9ADBD5B36FDC82933C9652D647E3AB1E32AF89E9BEEB52CD2A719FF1FD31996E3A0EF89827CE3E59DFDF47E97BE4AA957FB2D4494D616D1E2453FCB5D840B28945F2AD82C3DB197194E7115E10CBFCD27C61B6A99BDFDEA390BED262677C8FF7271CC3C98C6F1833C8FF2E937617CB33E84D7197738E1B1BB4ECBFD734AF7C66BA51BB963E45D9BF796C810AD62D07A28149584A7CF37C718A0CA02EF2A425C30FBD475A133DD9AC34EE08D150CB9B094809C9FCD90C688D0FD749DBB87B8F0E5410537C64821909DB40C7592195E8E7FAD956596118CB902EC808B910928F2E8382BDC7E0BBE5FA617250E37EF82920DE4F9EEFB79731A23F3E9FE84482566F51CB6F49F8FEA8826182D03061717A4EC58561696B6AB06A284378338FF29CADC6A0655BA3E0FFEA739A6C8D8A4B72B55B2A99AAD8D628BCD28D10A53A54A6A30272991DBF109335B96ECEB4301A5584F57F35D5A470592FB20708C6686F40AF6FF9622AD87DFBED69B7FEA33D169E8A8837DC5AB33D30424C9CF2917ABC4458AA442A7B43F2586E567C1351E4A3488C62105EEBC93E0373D3F8B1F5CA2B704411058DC40F9C33E41B32B700D29099F99F98AE5B693976B39C1ADF2BAE1AE67047162D1337B6B4EE5111A9A3CE7AE1FA0B4F57553468B400AF5E37C1D1A27C2774F15F699905FF17A04A126C1C26090FBF695EBCE0CB9850F8647E9F1E156950BC6537061B70ED0BD7D9FED822F55757E8779645BE4AF8FC2EF8BE62575A3F86C596FEBA80134B9396ED5F4692C39606647F06FCFDD65B585320C9313E35C88BEBA32115889271E615D06299680692898908540B4BF840CD744A47BE305BF252F41BEF23CBEA46871F59858E806A0F1251B1E3253A0B7D9790BF5D76B5A16678C723B1CAF5F48B0B706164D02696A70C4019D34FC7E72A8BE73C0C025FD43FB2CBFA417FF759DF3EC8523069B528BD0409102761F37222FD485933B3EF898756FC3D82AF7592DBDADB3269F7F6EDECC9AB34A84DD50B0BE923618A442B4248C51B1640C5369FC162DF4238F8CD5E4D9A80055C9B498CD3B1B6CE8BB32DCA38D1AEC63A27150D8737CDB1E69126250F4179358D66875F8AB2A6659047917BDFEC6E0EA68725D56CEF9D0AB43B927D32F15E3418AD30AD02ABCB6FEB060C2F8FF4023A3A80F17816E2534619B72F9C8FC513375F66BC873408163040944625631309A3330D56C971E9C12A6FC56544C155363AC3BA72A4D64DEB72B0D380C5F66C5047425D2239B982406100BD7A020555DB54CB67F99E62C69927D4B908B4646D5B8975802988944EBBBED2566D07ADDCB6E01DBED97BAE32E86F6477C710423BD29EADF991649BA0A11BD793B7D91ED3D8B8A049A58762F8AE35A5C41A5D8F99876341B3B04E0CA40D11D71ABADE17690D3AE9A450181529C0DE6CF065B27C291CF72EA1F01280B81AE6C4F8DE7950D376E4A2556700914A18889B9D82BF83A7AC940288F48463F238AB67957BD9725C497A5AE7EA045E8E8CFFDA186CADC3B6F563C12695A5432BBDD905E2A61909F459352B9F542D85EEC2D1BE513E824BF8D5AB7D2C2C0B3A4C75586511D41A17BD09F5419E453D8DBB427FC1DA2B9B0B5361F72E885B022D787182D873899628446C6B1D16D49CE6527C13C21625E4561FB3491620A2594E904150FEC295FE1D74F711E74420A7015B9C8C557C0093083D15659475A2C1CAF99E8F150BA4E10F052AD153251E653C89B14B956294B428BCC7E465AAB050FF01D8D18B3078D2E683BB8CEC1AFC643426D85B12ED4E918E3C2189E4A739605D17EA9820CB02F8436F46E8FFE53F9ECD67F0E18C575D35ADC1DC08973113A32CF10E7872332F9E9EB3F147AAD59F93905302C3FF5A7432AC1DAE30A00ECF0E0A8BBDF775CC624E3C3DF99A74893BAC2522EA489F3C84323345D67699FB4FB721518581184B88C2DE7B46B0CFD7CAD7A6757C2F0D3A183CA67EC22E2AD19DD2E70C3F6EC7ACBAE1D3ADA166F766095318B5214BB1D1F56C853D18F4D3CCFA7D8690306E8A59D0BF3CAD0D85CC3335046011E14AB8B2C63636A5D2122633BA489FDAC878ABAB3DA3E8A4A3FC1EA0AC2FD7E788F54949BCDCFED4700BF361641BC37656887CD2F57C79075A9C775DCEFF4E30AF4195C7CE113E45BFC8F5A202ACB6FD44F2CCB2A9B780CC590BB3B36BD209E6CD306D4743F734347B041D1F716F6A4DB28F7346D27FC552EDA3096DC3E794444387F21B24DE6B2F417275BB1F5910803DC84AABA8D61E17C8FD676BE65A9D6E71FE245D88D48075CBAB15047EAA8E95D45140CCD870E3C4AFE32C13B99F5CCEA912350552259973F606B23C181CD61452CEE2EAD662E0FFA8865458B6279DD415091DEEF00347BFA624C400F4AB771948AE0F09845338BA5B643DC043C6DB014005AC6ACC149C08CDD8097928D5095FE6281AB042345BAF715750EE5B0CAE67E3D3EEEF82C032C07AD670FE6E04FE71315B698D17070DA615BA17F6349157B01CA7CBA4294CACA566AF8547FE1FBD6E066A6279D84D0A4A88533F3046CDAE52FE8AA8C81E61A1E614C4876460B6BE3DDF436F61E760F5233EC63B814111025037856462648866D608212B9F68EFC7341F8054E70202F833FE90DEA991269E9523B75566F46AA790AA412586AF6CABE4E8D53B17CED49D9D0116E73541BFE26F5B3B84292B6A6EBC46E8737092E760D640EFA09E0AD81537C1712F894196B30565C9439D8B78B36F0EB445A1C99FDA3C187EDDA2359D31D868700C2E0B9F2453DF691369009E5BCB14A95D89A3D7856D920417161F3DC3CAEFC80DF8F485492E615616284F4667ED60073D360B98963E4A83B145118D5C6308BC78F6582B3E258475AD1E5BEA67059C6BFFCDAED047829B5FC319112B9F243ECC363CE85053C445FADB5DE88318DAD27068253B2176B069B2985247BB6DC4D5D4FF550461741B92388142B6CF549B664160793D493BBB9E4AA729CF6721EC88F1B3D768DD6DBA755EFA3EBFDFAA55586257636EE767BE600127C1345502B24931D9933D1604A37AC0A1A437A6508708C9084B91047B2EC263E38629E32A1F8DDFEE588178257EFD2ADAB40C3264EDD4200BCADB8A13F2C68B9E8F6C84764BADF05AAF32CAD2E4E347F83D41B66080AB2AF01F76E512831CC08978568C7D2D75206E256194123F4C4981C6BB3BA9575937316969D2825C052515FAA7519A186FC9564C78F912B1B9B730AF07936585E64630A409DB1E370328063A3251F65771EECDFC087623D848C0B4C5A008719697425C4C0F13EB421E3770BA741AFB080B47C264DEE07F8891AB549A52C15B9D4D35E07A0FBC1F20BA6BF9F8B60E9D26F2F33973BA26530AEA8CD8CA259A1212D0A1E7E7675E8A792B4A3CCE5A5F0420BEB962D8B8D1D93CAFFFFC4BB8A72674CBD39F2B58A31F69FABC9ED3BF7EB8744E195E5FA02BF2F83B6EE5E4061AE86290FAF77443A175B9934846B09879B011448E75D248C338B6670C4B38F3566DE632002E1D8C09652C0BBCA8F0BACBC6E3E08AB001C06647CBCCCD2ED47FC92A48B4276E516794FDFB9F676EB2040E15E4DE78D8628FA2A8E89BB2CA517954D47C99800075A3C443BC511D061678CF7F06EEDE0FB7934CBA04893486E7CBC748E10C1E8B8ECEDE0520159DF7D0CD06D4750EBDC9105312D100A57D83C4D59A2225B31E6B006D90A554F452A74F4F01EC29983D428D5187468A5A58B54E128DA463201934D7EFB26F75C2446370E24AF9BD0B3029E72583F2A6A6C8946616B1966FC8C52C543EC480EB1FC6758502B1A0622BE55516CEFB71E0E19DC6983E03FE771D5DC0FCC91E06A4A70AE67FAF58878BC23CFB546692A11CFCD113D8702B9FE6B15B19D5D0844721184C99C84145EE2901CCA5419A03D0C552EA0FB871E6AFF64EEC57E969E9896D39824095E2CCE2D9CE0FC7F3F6E4CB5448CBB0958E4D63BF8BD8F25070BB6852887D827458747F245DC86B2E22F972683507E81993317F08757436FE3EE5280F79CEC1A99F9B2CEC7F7AD4AD742023882E869B546E4613C3CC33E08F62B89CCD60C99EA1ACBDDEE68A0E39C9271A52802B35B04D807889150180B08F593FEB90DEEC0EB99D49E2D8CF9059E8366A6D44A3D76A4EEC5D6328524ABD82BA11E8F850FA127B838C46A4EFA37227EF6D070F0D52F3123B1469E5EDE466EA7F0D80932BDB66F30E275D49406544616528EF68C30E3591A9BEE3B4ADCA7C7EA17938118654BB77EAA79677D9A881F0A50449D06B1BB0DEB3392ED451271BDC9618363779A5A1A14F8345F7A4F1EAC1ED9A59B0C7F602E408624341A12D4DBF7CFF403E72D653E67D959E63F14C022AB450B1817C2B7013C72A307D12EAF0391B8EAFF728239918FD46DF78696902FB31B8D66E53E11407FB2AE55A8F1406CFE3F27E41B0020527BB18010E4378F7EDA482423DACC496606FDBD949CF8474D243343C4CEA2A89A6ACD9CFC249D7C4C938B20C97264DE63EF93B0FE9EEC90E3994B424F39E739022FBE103E10E9687E663CA2AD1EA04198E6A78AC13B34EF69988373F2C1C378259F567FE6CEF739892B8B37CC0ED905A58F4F39763DC6BEB85A917AE99A3EA1BFF39793F2C176EE4C872354F61DD6E98F0C28E2094AB4259B617E4807BDA1A76FF9FFD2B33ED7BF8A15850A9D02A181D05036297C3F0BAA45DB973E2359F965A9FE0FA18997FEEEF34EA9F7EE701077992F2B41E87122DD784E23E2CCA5A8FA5F880B6E192D61E79F8FF3E5C20F2AA71A9696B1660093118DBDDB7D413D0002AD16BB49AE1B13AF8E05C643315D7A174B44D38A0FDEBB87B6D04F66848B08187A8C0735A4C5C29F9D75729250A8B856E57BEA623FBDD098FE2E185EA0343CBD105EB7C62BBDE70F28D3260D6343DD1C73D87E3E4ED3B85DD585707E4C08838FAA1D317A840269123189E4B607133364AD7EAB7427CC14E51AC0AB1A7C883A48AE7128093247615807374B10C73F79FA08D0731BE4F21356D191782EB1D10DEEA5929523B3B4D6D97BF397572E7CEAC24CD55009F822EBE800A3231E1E1FA34A92CEFC0D60050E04C3171859E54BA888D2F670E22EBE926B0B307A65264FBC08F8
+ct = 116E1BB0F0C3FE6577D0B9D0461E66C6A97AB9238C52ACCB264E5625CAAFACB97B1F7518CF9E98302C3EFBA431433AD5C9B890584419DDB2D3CE392776DF7CA498D504240EA38ED5D3248796A050449D792E491E4982592DFE496D72E9C7A141B5DD106CB594D2D23DCB557BCBBD845F93F15E3FB99224A300BC4599EE8D0FF72A063C3B07EF396FDAE55800312CAE38DF437BAB9B7497A32D828CF39B022B2DE60330C697C4CC2427AC2CBB7E3867F6058604D969A940731E15D7BE6313617783FC641C5C36CA8BDF91533E23C89471836FEA65E761441535EABAF2E5D921ED05B20208592FD41CE89A9397BE23C999BB0E0B92A091F45C8740397E8F739886B40441E2396AACBEBFD26A60F5D10DB9D1C53DAB5A4691DCCA909087B824E48E38C702B56695C4D0DB02DCC654CD43FF4D87550E071A9A05EEA07A13347F4AEFF9658742C25C038F1A10F1611D95C1BC7996CDDBCC78F4B76F0F2D32C22D8DC1AF5077E4F43452595AF81ECCDB725D6B75E129CF268B2E48C6C2AEB3942F2626290C5A18AF70BEDA187247FD58D1F4C609F00B18675EF0012CE51FEA8706E9189C9EFF0CE743E8994B38BF192CDA8B7A4E743C0001F9769436A1CEEE9EEA170F6EFE7810CB8E139A0521CD5E9281FC8068C09153086E3EBFD4D66970F098ECEC8058FF39B914B0BECA12E18D97499D717908A3EA58E27260036847AC90C7BC20284F86C5BF5C9DF31BD021810FEFA6D909DBCF3C629128FFD28BE8BD8EA3FD2CBCF33C594C6359A351542FE414978ED8D75D62B5AD72ADD896F83EA50738174EF62D71BE942BBFB9DDD4E34149AD16B41B6276BE7A0AE1F793E7153B527FEBA4D7BB35FC85F05ACF06FBB53B7A1EF4B79E6E6F56DBAECEE153F1AC81BAC700DF257E7762788A79C5B809F30B30E7445B2F200BED7A8EC529C8D0EF77E1163B8F1A422AB782881490C6330F1FA74E49A1554F58907F550097F2B23EE7EDEAD32B60D4412819834CFD618E6CBB05656DC7CF655DC49C66DA4DF7ECB32A61196378726EA82DF97A5B69B2306B344BB4AD3F20AB5403279077FF5B9E148711BD1622ED819CA60735BAC3710F3E98B0C07D08C71F62A4E395FAD65610D55358F8DC3CD8A19472973FD2A620AF0AC982D8CAFF377F392F973D2AFB3B4B888B4C7CAD219641930863EA154D43AFE3DC55801FC78878258668B1CE63A1C6FD85D851D90293AACE975E59AA908AC82FF9B9C8F0CE2B0D4363AFCFF3D1FE0579480C463F9F0B0DD3039FD56316A729FC82FEF3565AB6B655C270AA2DEA67A2A8505ED0EBD209FA0A58414A6B39171685C2412DDB4066F1E635C78E95695C3BF7B9E2C967C09FFC369BFE7EDCFE738BE4A19CE341A53DCD525E7502574B86EE07DF0E2A898D66B09B9A14B0AF50159F72273D423BBA7E62FE8088CDF98EF89AE851429088B723C25C55600796C7FEAB7D2753CB0B7E0FE2FC78DDF84B6C276FA100B82D6886B12B7991926F6AF43AC46A29D34607532AE23F1674A19949E6D50AFA27A638794D2A8807108797BC15E4B4EB7C3CAF420A952772ABA1AA8CC43A8E5235558AD7B2B4B7DE8DE7ECDB4904864CCAB2B98C8BB877E3EE25F2748C93A6789165EAB655F4C086D2AE7D47F895414C2ABA2B7DECA110CA0B07C3D63439A4A720902832AFA39D0F2C51B851F657BA778FF74FC30502322A8F3921BEA908AB680CCB3757F40F17A74DBD75905367295DD9048F5CEA1DBDCA9E02368EC7C8D98F747A0EC82DE97F575D0443D7C0B42F00A394F9AEEE02AF9108FC83E8B29633BA765EEB13C77A2DE42596DD927B8F41D47E412C86A50935BC7CBCD12681C4376C17638B6B741C4E74137001F81457275F7F66660139CB0E000C45C80652B668BF8F5A0D8AAAE731E8FB148E5F784AB55BBD027CB5E2764C5C66AFD2BAD10433878946461E889364057FA30120EBC980A002C4342067960FB21546AE405D3090BC960454C48C2A667135A6F4701DD6014D6945239547D05804E3BAF939066FC76AD8ABF637E06018605D699461689712A8114C8D828748E186D2E0B00F8CC692BD10D97751593D4C5353C7A2E4CEDF8A831956ED0CC3FAB8C62A8F7D9F9ACA1B4931AEF7C08142E16E979FEDFDAB837A6B1F83E0B0E632CEA642779264DF88DCC87A8A5CF1E22BDF21114A6B6A9EAF8DA66889A314EFE6281CAEC0D9648F82EC4FEE7FD3EFC0D5E2D0264061C99E5481BE78679A3955D2292AE2AAAC0519A7905CD71386CF2B6E1BF447D1E46FA453A917129469B0A6708617DFFC782E9B62E647E4FB363DD69AB25E816FAF616BAD2B3ECE4ED04790838B293B2D55305F7FA7A5B4EE0961E459703CA49028D2AECB8DFB68DAA29BE3B241BE17ECEA0B92FF8321AE41E01A59C8C06A399BCC7F94685125097487DE5524FB41C6CEFC48AECCE1D49AE7CFCBAD8307F06355C0733F767477800D0E4475A0EAB8CC5123661C39C99BFDCF02877DD39F2031B5B5886AAEBD82AC51A9C2945F55A3C965D81FD62D69F29727234D1588C4A3963753AA13E823748931B53BD81B1C029DACF00ED1027F7870B2D2B6D44C9582F39ABED4F16AF223AE63D375B8FE84301B356A1F4E61D18AD6F07AAA30AE28E294CD09248651FAEEB85BF5E15965CFDF5459F957E9F518F8449F2B39CCC7A9D3E99BBC9C6A0F605B0181BF2CA40B81DB6C082B5E5F8983D348D0A4A49C1789C02B8C6D196ECEB366B83896D5057E20621B0B75F1BDE76C5F47714E4AFE3CF743C074BA11329B491BDB52CE07F77C8AD9CD746A938722A33B461BCAC7AFA5234C7DD23B9229F0CB89C671CBCA1F9543C821F281FF903F43A6D27D82B9FA7AB982E78BA6AFC6FC6A1BB6646CF65FA0D2F60B8767FA84989EFD61B949AD37C2494D8561DAE9F3ECC0D45740B91AA6CA3F2C11305D272551064888B7E36039967F39ACD91AE0FB21B17D30B84A0F113B0904CB9A8D93CEEB741B388568EE22EB7009CA985D4AFB47C86731AF92ECAAFA546FB81015DCADC73E15194B9DDA7F93824AB91160F11E4370EB3A735764AFAC9AEC1C132670E3C92614D37BBE2727D90E07A00505CA2183D554BA9C32870D746C0495D05644570724D2EFAC37F63C84DF3D44018ACA23A353701C9DAB191CD706312E5A764E1BAF43E2AF73A0AFC6486866EB7C315BB9BD28CCD1412D58FABB9854EEB4E84D99AC19BD52E88F8B2827D7701AD2BDA5A015341C8D35F0839E5CB4FC7831CF1CCA892DDD1454FBC35BBBFB7A1B79788352C65F1CF03A22461B19B5C38D900074F4D3BEC0FCE2737E04ED5B4A8890EF00DA64B2BC896B0698634F85C97BA78646BE1C751385BDBD3495B669CCE5FB878AADE761FE0CD1170B389BF46A1172CA4307F101A80931A99A94CD38C5E138782A8FC70EEDA3A67F07586578203CC8B374AD8C9C996D5080A846CD86E7D1E6AB8C9192BDA12D57973EEA543D25A215F85DEE4C3B4F161384BEE567862FE62B4076267B9D6D2CAA20FB7FC268BE702752C6D8E3F2E489D28A3BF5DDA2EF16750750D35AFE20E692D8861698562C585D959299638E21172E68879FE9FB75A9C6C8642B84A31E1163EA2A307D0CFCAA88BEE7A8BAA5308C5C6405DBF5991C3D4D9E94078687AE4D30BD2858B7F05403F1E5FB1D65C250DAC2E58B3299661EB1EF9BB94D01E83183EDD1A405F405E3F643BD8C2AD4DF680884BDBDA20F19540028CE818D32C3D4791C72CB22CA0DE21D92FFB7A214EECAFB7A4EDDB012C8965F66AE2C2387BE94DB86CFB00C742BE7A4B496EC13C5CDB4E32B6186630B6F34C876FB8A5C1A923F7DA870D3D29CFAB2509F9D7CE1E8F6DCF8F1C2E9B9BD54B56CDAFDA5D96BC42484FF3776C9A050257355518E5A4E19A1887416C515B6EB11724FDA244FB9471871CA0C44A40229EDB0F97979691B320037E8F963D64847E55842CD746302DDCA40C3FFF317EC887A7CF435F44AAFA7CCD94492A655261DD94513C9CE019C9D7EE0629D89C545E2ACEBA097E656558B7BA87BF7502018755275CEAA1233BFFCAEEF34A6E77E2132C0607B456D040E601A3CF62680E966AFB05B76E66982010C9DC158FD34C32C117EDC65884C14218F6764C0EFE2252BC2F67324FDB5F5CCB607F728B3F60C1A11D16CBAB8DD48261106DCD5D3F679641DA324E5AD5AFEFE8F8057A7EEA6A3B720D1F2DC5E02F367E3208874F048A761E056D93661C35308B412EA53DB2489280660F8063CDF70F08A02127BED194E06135D165D040C1C997E7358BB2888981C6D22D2D57FEAFC9D7CACC902A9C911C3DA0A3DAA14474A0DC57E28E788551F6ECB69E715C1A74E9AF79F47D1C44F2010CFD818B3276BF04F480E55726F79196E2F984B9A412471C89C4B2E282859923788581CCBE33594DEF825FC03E259B8E0B17AAB72B6897D297DD3553CF00D11D442CBEDD311D5940770A64A2AD9BA147D5712D07ABD4F59D151B371BD0016FF8A78AD7A69BFF6008832B73366D51A0FB272AA8D1A700DF88D2941EEB8FABC0AE31373F87B9CA7DD5C7A54DF1EF48632902DFE30B072CC1253E5BBECBFD31E207FB430CE1973333B804634E180AEDB24026B2C837D1972C379A6974EA8320103A7C4EAD8F62A466EDCD3E18B6655177549A50CA4C1CB0D809864285D13CA4E99EEE366E93FE9F35E47A75E32F496866808D930B7FDE453D5CD3639AC506ACDCA62A4D14AD8427F68CA610916D2E980E2F79413A29EA14D2176BD628506E6C653F1CD656855D07B4CF870778386BD02E827F412AB25779E63BFE49D0106770E3CFE126D073DFDC9338C7D4501A861B2BC7EC66ED19B39E10ACBFBAC6D78C1A946A6C424D2495DD5D9893AF4506CB855071B5CA8CF52B218BA0EECDC3FEA080B8176878978D8DFFE0EFC4835C49C5DEC189BBDAB5E9830B0628333A0FC1D7C12DC4697245D8F1E0FF631CDD21D54ECBCB5D3A8DD3FB1029C03E04E0228D79800397B2F860E2F2DDC531E00928C8CD4DB61607D6A080F596F965CA1293091DA84BDF9B1A9D5F524E569998DBF3CA7BBBC90315B5FD6BBAB6EA0A2FCF7C6D50C29BB5B6E2A64FF42D05646C82337F3D5E875894B363DEEC808C392E9548FD2E9216E1C36C28810AC07FF9C7C836C2FA4A8305DD3BC082CAAD541E5E82B5BEA1FE4EF51050705224D121A26D23044F4738D201081B429A4576AD17FD3A7D5F4858DEDCAD7493FBF7AA7D4CD2F1EB578FE8826B007974297D135AFEB286A08D5030585AB31E3DA6EDFCFC291C4E1CFC97007B6CF7A5A1038764486A76A167F50AB942A18CE0DB993FF36A33A228A60D044AB7B8934B2680DC1D0D2C9D5939460603FA76447C96099839513E44BE01F54F6471F7803925D36CFD48C699293FCF1DBC42801F185E43968676A0E6F37B261A882D46B0FE5873C8B8C8847A93610BA3868BDCF57B4CCE1197B3C75518F822C778D96026B59F84386497065CB77EAE0438B38A6B058DE7903049F5ACAF6E344B8F0610770915EB64FD2A3FDE751137A3287819DFE6B2D35361E294F09D65A364937E5C2873929AE7DB999BE752D5E86D3FF44602C299022CDF5FBFA9A3146222DBF66C396055383670C1876EC24102D34A57267A9A682120E5F5D8C465F886BB576548B1C8B138D50CA2322691E02A95D6E4B844B747EF3716BF00FAC0FAE13A8CC2EB6E29190AE74FCE890118031C0775B14E554B42D3B5F3B85BF0048D498A515A5C71E0298D42DBC35B5F6C55547F7240162BA453660233B60249233622E7785898231B7CC2BD05EF5D8303C77F5FFDAD9C0D1B68B45F91391DCA4F237EFE2C2FB2185A8334A41AF96996EBF568968827D98EC93A339C1D8131A113E6B18288FA24A921D38548768C6F77853A97AF96DAB4F62454D9E3C3E27396042CF712FCD47054D90BCB612AD15333C3C255E2409C70E1CAC66F567B46E0F2E919C5226D639E75BD557391CE18547BF1D941F62D40C376844100F4CCABEABB7F9C9D99E61A39C7654CC0DB1812B418A85A13FDE13935C4A3DBD9A6FF2662880809EF14901ED6EAD447D267A60E13BEBD999C6E39054F05694696FCC85704D0F888A9BB70FD37F18A264E4B888E5C8CF455508DE24834CC5A4AD3F26C4A334A5D2DF9CEBB85C60B6F51CAFED4A3AD2A53981757B00F2409CF92C682648AC0E84663D97F4D0004A3C1F041EAFFD9DCA2A07E79E18EEB5AAE5AD32F7D24361A5072C984B47A42E89710489D0321B01564D68A68A063D3C1641B6EC0EE49014463E6FBD70659A3B74AD15890FBC4C653A2BDEABC048AC27A003562BED82AD0EF00994981D03F9603450CE301F3464F66A80A986A9DF8B10044160158D1F5F35719B66DCEE42B105B747C6C860FFA5451DFEA407BBF744467BED6F91B03671AC6B80D6C64563A178EEF27C978283A7DD53D27BF3ECB68DA0D3A2F5D75BA86171258D889D2C3870B7C26ED74AF111FA0096B5A82E17A6AF2BD6D3807B685F4002D3AC5551D0FDAA1D4E85A330C246B944227C5860C99D8C621DDFB4FE6A914A1168F19498682111BC26D8AB6BD847FCF929E4DB87C5896860B274B13C037E5BB212D304176844CE955F4050F35974B813A677C448B41852229FF4993C9E9E6E82018A8B76FF1351E0C628F691B5397996E1ED6ACCE0BC2B0FF678C463AF62F3D79E847307261639408BED7572B4FD95E9F2A784DF106E2E4140F77498AF252FF306041ACFD9CBBF1DB10EB40102C6A26C62649E155D031A0DCE629E3D5E3CFC403C4C7B47E6FBDEAA23469C342A9EE6A3F573514392EFBC0724F5182FB9001B4BBFE5570B7E1885512AFE886F52DF5CA1339B1655AFCD4F0AFB058FEECA459CC1E72460266CE1FC754722A9B826F6A3D250676DA59AAB06924B81822834B031642B0DF077A884B4BBC8883D50BCB8CA27D2CB709EAE22D527CB374AE380C0CD28B2861C53F9E86C9D17AA1A1299055EA916D80F512253997DCDE1D59C2FDCB7541498B4BF4B17E2CFD9893FD992E7C8819C42D3306D855D8685EEBA9D3CDF196D1EDC63E02583C38727D744CF5E8E0FEAF1040AE4C43231D7FEC800AFA7285788DFDFE0F68A24BCBF769ED29DF04F3FFD86794090B7AEB8AD3032B8A0400DB6EE3E09114EE61809ACA14B39CBDFC050485FCD9C909CF0F8623D9A8C62C6658F1B3B295A94B5FFC4D9846070DD81407EECEC4CF2AFCF0D22007FA1FFE39B0D0F34CBF7A6D92B94C5426962D05081C65442EC4AADA819B0E907FEE8A4D68C7B4A6FA97A7B1DD1D0490525D427702397EA8F79C2359A02704D76C5F69252C2EDD8FBB4758B27A619C4CF7FFCD4F52D034C5CA1FDA8CF34D033AA91CF541159A76262EE48698765A51AD411D689B4B76D2D4A1D7079C7C4175FBBA39D1D5059DE334B7B37C32B745A30796DF949D607BA779B6C267B5E21BA48A2A8802DE617230D458B2642268CBAA5C9C971D785AF63AFEF502BA189AC97722652D8970488E1823468105D1C37C92AADB418B39977636FE466CDF71E88D7B645CDE41E38088C05A81E13FBEA89F3372576234338DBC34F367C20CF2B6CA0781CB8A941A5EA182B9E52E02FE8602FA814ADE29870AD8F7A1CD653B7171EAEA5B6218C2E69EA0FDE416E34F1447A482A98F83DA4D0B4E8CED036E16C17C9C685E05F678783FD577676BB4F8E9991F37524C3292E50F7934CD39FD2AC3499CFAB70AF52A7FE541B1836D0D452B8C416E5C70210DA557B9E91CFDCB73E5ECCA1DE69A0C15BE97711DC45DF088EA31B55D0F50B690C15733D5EACEA2837FAFA54DEF8035DE12B224891515D26F060F8C7A934D8D43EA689E182258B3819803E6C5B01F3A23CE2E504B29F06E0E2F9993A0A6B78C9C582E2D5F084D7FAF2D30DFC6C553BA33A07F0B1D1264FBB8640B1CABBFFDB8DD2B2C9375CA07D3F50FC03937B232D081ABD26716447CE5DD8634C1D7BAD7B9B6A54D30F697ACCBE48555B19321A4361858C0837E41C63ACEC25ABF8309C9F7651B4C2A8BC245EB0D0B196F92CFC48898787C0891FDD045B7894AFA34DFDA13BE0F820471A2B2EFF9883D910A3ACDB4EDCC021077E67954A92434923B132D3FB8DF3F8ACA164F49AE9563C2F54E1C0631F08C74265FF325C9CAA15F07D87BEE34A0011DDA64271D632A08B908ADC534F9DD0E5D2AC5BACB44BDBF601D8BC641E56BB69B938B8BB12157F6C8EB1FC5230DB235BC07A4BA21F389764ECE975B63858CF8617EC5A2B830B120FA89809FF725793E643E5F81FE7B040D9DC3DCFDF2F40B3E9C14D646ACE37DC8FC59FD91471FA6654D6F994E0A0320DAF8ABB5EACECF31330B68FE00B26E4BB80D4359F3F08DDFA868F1204BA3E2B8B44E16ECF4E3B4B12DF114BAF4FAA69173140E6066751E41764C21BC86514E2E8143232FDFFC8ACCC233ED580D2238FDE719DCA1B22C02DA0E7BD9EA288ACDD94E05317249ABECA4E13662C84A14CA99784DA0098EA51E2636B7187657D7CA2B2CA0E611D1BA5D4EA436F1BF041713F3FAF143A301C03A57892D2F4B3BAC61D3EA9D480869455F0DAAEB78E2BCBA7767ABB54A2706721BF13A454B7190747AE9E76E0B7EE4788402854FBC344D12804C15545EB3E77BA105EB5960AE1266CAF606FDFD5C759227084735596DD42C36529616C7347623ED809CC6C03CF06D22B1A36A9EDA821C59820D5F9BF857CC4E3188AD43A49659D3A349305DB33A79365B22C2BC6BB4E9E5535CF10EEA10FFCBF04977374B89EDBEB13EEE7448F9DF306518F6E01E0477CE4EF77F55016924ACBE6D47F08A8E869853FC49AEB2B23B5DD1B47BB5B7A2C56FA47E2704B82BA16F4B42BC9D4DD0AF7E231C2FE60F85BA003A3056A2B78000A105F7446E395C0E195608902EC156A66F126BFE565E7A1DE5EBB2BB4EC1C3F52717F6032C8DD1140061BF03B4477D3CDEDCA7073F398ADEE0F3BD38BCBD2BACDDDD28037FF966E2A08037CC6A1E49BC9390897D6F171BB63C2DE1B7C9D15482F7D633C5B6BD71587AB2ABE110B5392E9AFB07F1E3B8CCF166B831897D895621A9B5BF2DA16840254BF220AF991D1B1986C18BB301731EBC8DD1F0E86E074E2E34D5D6B4359420AAA8159FD3163D2A88EFE25B55B70ECB0182FAD533B12AD1A93A5A5D979968A38FA40BD582D051684AD2D7C5E4A7F5A969D8C0ABB072948D93C1EF74CA88A5BF7FFEF2F05BD774AC045DD59689A7B44EE9C91360338CE0ACB6A89654833F40B38B504E71C1A21D6D181EFC1EBFABE873FC523749894298B597ACDB5358CD50AE922EC4B0D4F513D275D0DBA66A14BB9DC37F1D982C6269A391C56AF8A2A06F2C5D4B034224E297CFB60AE6275AC966C66DAA7A49BACD8ECF5DE88493FEAB69ED161D3AD7F0510989B85DFD1BBC3E5599734469ABB12068AF658F658D54FF9F64932B3EE2CB94F6EF03480AD54F42759DA2305C39195F75A618B419FC9410302E0ED0B4A76CD2B796310D880AF178B7F5114ADED23C55F358201CDC65ED394A8C07FB7D9B51AD0FDB23A02A52B8C353BDD59DECC48BE016D9494F8BDA7C6981F5795DA83118A691D51DB128BA344C44A12B95EDA7F70A6909C44848106166F3ABE16491E4B1671C627ADCD5485252B24B26ED7CE56C54811E5EB2D0ACBE92E782426D3734E21B3F7E6134EEBEC2CA97EAAD2659CCC03325F93B56239343393037D902B881D0A65B5C1A0B189DF73280FF484A391F94F9E39A8C5DDBE23A62896CF27BCC6A5584765EACFA7A0EB5CA768E6A5AD27B6ED1581863571C8C50F066CEEE24493871574CE9DA0786C74D699D0FF99C957954A72E58AA14D35659BBFAF2068E9E3E676E917BF982F47117B148FDE36E9BCE3D3FB005B46C536A099F8DEEE0A4AA38FC81338180E2BF83845AD7A801B29B6AF2B6CBC82E59F1652A98A75E632B6150D0D07C524EFF2D0C561E97730CCE49C40320C6D83F4B4CB0DBF858DD0D723956D3B114975485A94487C0623B4C68796B06B4BEEE5885B079E63368E5249F58DD216699153FFBEA88B417A666BFBE8A6692EC9D6D64A1ABA4010785FC6EE3B7F41D3981F3867C385C65CE70596AC5769BA8609084107EF6B0BA9C752E1D38AEB5AFBAF105C4319B0897B09A000F59D6D6F89DBF9B36FE9BD84FECD661C50AA5B8DCFBC1ACB94FFBE5CD5AF1869EB85CBFAFB87EACDAE8B15902F6978FD2697E4451121FA6F7748AD25F0D906A6DB34D966663FB05484BDD8F2EB42E466465076D1636BA2904133A596C1B0FE855BED9F52BDB1C1DF2F6922075424060B3A00B0EE0197088229211FCB45252022418D2FEC26DD435AFF66BF2A148149506DB53AEAC83F916E7E9DBEEA816DFC9D760F1E2FD4A39EE9CEDF55CD0B3695E96F3F739EE32A9C7FD4A16DAE4A2637571C33B50B2EC225F2DB4710EF16C046EF2E92F162682693C698B9525F0E99FBD200DE9D1CCA1E5B7B898B139A897A25826D5FED0B9F48F490096332FCD06C97FC71A2BF2CA99BCC2D0415064D572EF5184491921F4C8D276449415E9FE540435413239252801E0575B8CD4800F0210CD7BC35A2B3C22AFB1AB294C10AFC290777438039CE37B122FCA90DCADCC6A284EFD924E05012BADA241F67BDF98F8DB6067CEE8891D9C6A1FFB3397702C7D9D2332EBEF530B4B3BFB04514C470C10523D04E92A70DFAF3B9BCFC6BAF2CFF62C329B228E17681735708C7B3F22A0BE34BD2D9E25305A9E1669EDCE32088DB1B1F7F38855611CD27FB98C49F1C0B4E627057A0489D166925C97C4D9F8B4224AC9DC92E22226042C99B4B390D0FEEBAB7AF8D1CE68CBA4A5AD3B95DB99C04E9956BDFAE052C02D56651501984A28932B3C97B520488A36FAE63F32838D74685D72C37A60BB23DBCF51D71C3A133E1AF23A7F3581A8F7BEAEA795339D6FD078CA176961435A8BD4FDC4207A793C5032001994946BE5700A38DC8B053FC94F3E4D5E6BCD7967106F9453B62984B875AF06C008C4BC05380AB2E5E7F9251AFB00B8F465473FDEED8FEBCA48C179131514AE88DD730533A87936D69DD732AEB9DC4E8B5BAE3D20B5E528C896A9BF68B3BBCE9C7BAF4BE9E1F5D15DDD4ED5912D5BD5DB6C98F2A477C436362895A28BA8485D50E199B01A21ECF805D2B5157C78C965171696D51B8CD945B31B6767E709544188D7096ABAF96E2C4658D9D99C05BF535AF2846C10C936ABB4DC7BBCD23551246DDFD598B479FE3534B64D17BAB73D7F6278F43C354B03781A30DF37857D1CC7A2EDBEC437C966A1B36ECC39031C254150D37D2DB4941F215D421CFD5BFF4AC283CDB8AE723C5DDA87DBC1E0AE37FB44A0710409F736517C43E37D118EB1752D57297787DE47013112ED268B0C24EACD1A8E86BBA51D3A91F966A4DB9D71A62D86A5BA7BCBF1EEA4D1D90A04712DF793DF42DC59DDC3281BA4E2FA9D7F7BC75A7B12097AA83C5BB78717165FE37D4BB1A9F18DCE1CD1A456C34AD5BF478E378ABC172E1B1C0AF003DA96A37D28D8A07425227E4C570CBFEB4B5A4BAFE870159843A046428EA0E3FCF6D194048DFD9687E950F4C178351B6D26143D79344FC5F581B82C4806E1600B379CE26382859655AB9AE31B26C479FECAF58EDC53F00F6D72C0289A9EFFE1E70966286A5BC37A5D7755AA9E875DA1101EBBCEDE20AE8F8BAE25BF874816A9DD847695A6DA23205C95842E9369EDED6C49C8F6E1AB8396DB9BEC2195B1B559E702CE070487E3E35F44830BA768E176025824DC6D7E3A77AED84620FB57F62BB6B95AD459DA86DC47F7EFBFA1E9A82B0C1F7CB5D07E29BC76924FB2A4E1DD00623A83EEB0C3A4758AD9D73347981849E45D0FBCC018E5926730C33B768B42D6CD24C776B3EAE888BB6550FE64D42620244022060C78C433B32249DB30C91611EC0BA79B091BFBD7F88E0C77C1FF5ADA7F4E5F6C23DFFD96145937FBC0A07717E37467D1A2A107044A506582B25D282B008B761C2AF2C5E8CD2635146205A613119883001D33D912F31EBEEBB76FBA3B22B1CF243069BF2271B7187616613671075C06F2FA8501669D6321E8C03B85A90BE27B77DABF76AB228217C3FA0DBDA6C7A98D524FDCD3C924B271536443C31D5663DBD13180C73AC781B6DA8F76780E292593A0341B72671C1DBE2DC4AF5D905A79828D9769E745D766FFEA4A2B971F34FE74B5CBBFDB5D7B9116A797618FB1DD0D9F35004627287936BDD7AFB693CEC6A054680D25B21E372746EB9A6883FA0492D890A6FC5BE1DB59BBEDC56DEA3912A3CE5A89C460A9FE8663038B13DF1367F194659F6E38FB84C7C0492DF7A62E3240FB8FEBF46FD8DF3A44BF03617DB741BE595A3AEACC1615B237F05B2DE3DFE7A82EDC376E6CDF7321E539BDF95AB5B800F6B441C33BBD1B97A8D1FAE3DAD8AA5EEC451C723E74EC89CFF17321FCF6BCEF4D6F3C289127D27CEDB765773E2B15BEE46594EE56B5AAAE7C49F482C117DE3FC15E2149969B9D392AE61B88F0E935DD24E8779796040FCCFBC2F40EDE62D6A21C6823257B046FB1CC39D3FE9AA724A64B1B223015460A8DA056E8A8F4B98B3C118DE3F3B9D7C89E5ED50696935E6EF440B4314B8892F23290BDFD627D666F2DB3B4A3EFE7A1B26BCD6B1D8B1F25C30971A0E429F015DB4920AFF0CDE77E5562A53690E53D5089A948D0278354026A04689800F9EE44139FC6C77C567FEC23741027515CE86E35571F5FFF32BA9C40BE676
+ss = 60EBD7334B5C208EE6483522049282390DA8701C938AE3D2BCF74AEB052B1939
+
diff --git a/hqc-kem/kat/hqc-5.rsp b/hqc-kem/kat/hqc-5.rsp
new file mode 100644
index 00000000..90a28403
--- /dev/null
+++ b/hqc-kem/kat/hqc-5.rsp
@@ -0,0 +1,9 @@
+# HQC-5
+
+count = 0
+seed = 9EF877FDDBE8891C6E4E79EAF022E563DEFACA6B152161B9A423E8FE96A403E774B2D352CF74C934069C9DE74757F505
+pk = 4053237912EA281C51C4456A5096589EC9D20219651E00F9704178F0CF84F9AEB6CF9B91058293C99CED29F30BD7B76DFA03877BB9E6AAFC179DC638C8292EEDFA122AB384A60D18DA42AA426D7B08031564B1AD45FDD32956F77EDFBBDC012894A76CEBED542A941A60DB8885DA44CBDF284329CE3D59576E9DD444F16EE47062C95687A29F76815E884BB1CFABB86E49C0836818A3AB9D59D0ADCC6BD3E6C3ECF8478111BAC9D35E3754390DBBDC30D8DB134B5C28044B1C66D7238FC9FE354224466884A660268EB2717417B0D6F523C7D54DBC178080A88CC4113093F22FFA6BA5850F83C0B965FAD1581227C37C8C48C614BF81771867EC49427E82AFF14B352243F92610D8BC9677167ACF5FE133806D8F807480B33A1EDDC41D5E26B902EC4CC5B47A232B51C2843166FB2C499D3A3EE402F4CC950E6A190D016AAAC955071F5B9E982B80D6A21E22A5AB047C3566EBC32EC36DF0C738E269820A889C2A48DFE70BD7EFA6DB069436C09289BFE321219771C03ABD46014CF48B1F033BDF9D71FF5FA80566AA63CC8A1703B34216F81E22150647229F649C3072F323BC99D051E3940CCB76F71BBA1492C599D3FDBEAFA5E0BA7A7D5847F90D31108CB722A2A9823FC2FFD3DDA07CCA6869E13CACCDF1F19992BEF53901ED5367DD457F98982AF673574B129E3485F64D80C18CDDE3F81272E53504081F6417D5EAFA78E2ACB67956CD9582B0F96B72F8564414FEB6C13B6E86626F96E83AA93FC231E012D3F97259582A784961DACC44A5322EB76F3AB8F2E66BEE375B94C2088480A9B8B146D615E215D6F60E95C97E2D0884B77739C6CD853C1A0D7A0EA0C00E34350AC4407FC1018D7704AE5EA8509F2EB4F31B262E6774DB45BBE008C5C7218C30133238250BD3BC12CA990B99C571345D9A271BCBF775534D072717E1DFEBC4AE6CC86E81412414AA1576DFC2E435C7240D133F0C1FEAE87574AA6DAD6A78CBFAE3ED13C9927C6D23ACC2D55070A91F9B8B441FC6AA1A46CD292F0B914BE3A4F87AC9742056C7099AC5D4E3C4A79B164653B0A00E30B3AEF98C7B50AE93B79C6D06AF8C2DB47D2F97C0A50FAB23BB0F461BAD0E791EA3201850EF2B9BA8CDB0F8881AC95D9C34A0CCB63D85F31EEF3A3042BBC6CF97350456F4E791C0BC3B767751856202A719C066669C2710954857EA7FEA97C7191ACF2E5B5E6C8F57A8B3802CA3E8B97D0C52DB3B7B392D2A80B7883CC9C281BD87F7E4FFC6998E6D575B077D443EC4C51E3BE443741D364EACB1DEEE36F82E632091E384720BE26658337B5F108192FEA007115D69E57221DC4187D1157B4C72A2DA3406F384FACE5512C0878A086C1DD8B5D3B985CB75CB0C553CE5FCFCEB4271319397630166A2ADC96EAD14301EFD852DAD993D4D63757B27DB3DC9DA5D5869B90BA01FF0F3B045E7AA06C25F682FFEE3724ED8B80170270283DE04BB2FBB5F22CAEA06EFAED21DE0124451478A414D57E9561D905D891BF794E3823BB6336AF8A6A10AE3940C42A2FCDEDA2A2B69C223E3CBFC6D87211ED4D1137F122A00BD713F1EA50A9D9C4998313BB391CFD8E626983F306EBB92E2474106F0D8B5AD7ED0982EF4B2F80189A7A18E9B47704388BCAE76DA6CA3E5404AB617AE9913CE820F656EDF967D436CE5678A60839C3A8C8942AC52E402769C7C01920B5B1FF8A3E6160AA74B6163A4F0520478A1B70A32C71B4B7A4519AFEFC44A70E34706D57A14F6AC06DF09923800B63091FD41CF2CF7ACD2333D75692E751268E8DA2508F2BA8D889F684F248D259835EA274AA26AFABD6DBB714AF55D532D5B8345A58A3DBB8FB431EB0672D223A49DE20910CF27FCA95FEF5FD6780452787478410AAABC4C839F0A95D85BD2904058483C1F789A6E8ABEEF638225F2BEE9E228F76C6D2B5CB77C74E3EA35866760CE032DD95E17C6AA201E1CE2C230C4F66AA0DF96A0E82295745493D31F1103EE48A7D0B6296676F8247EBD02240784EFD81A8BAA2C7A0FDE2375821E477EFDF9CE41C3B7FC87ACFA6701B3EBFAFB88FA84761E72151C183E2D148F4106B2C3A855B1F36C28905A9D93251D6F9544CA17581DA9A1E5BEA14FD9B4DFE9D58A7975866F737CA16A93E1742BC6C1A84DACD9C5F80FE449D3382CFB8E461946403B5E484653C725CE9050F7128FD4AA53FA2AE0056CD0E5A353117CA6F815B450482BE2717010DFABA09F9F4DD998BC5035F7BCBA4A3B1F62F55C158185EE529BB985710CB176673A2E56501F65A3B92D05B8C8CED55C3B32C7EA6E83EB6C251CD24362E0541F19357726188A0E61F89209E821EC4A9003A20194D76F7AEF6B3121DB699931150AAAAEA8D1943726A567970D24BA5E69F3684B4CE75C125AFDFC58E9CFBAEB00146D5BF70C7AF01E43F633B5F457C9C6F8061434D6C3BDA84894E589FA9740FEFBA92DCED7E8537191482DF487D97F833DACBDC64AFCC7C33DF8B622F035CBA42CC4AB79E16A5ED2967A93703EED0761BACC87C5C814F55A3636404A2A1912D442EDBE2B9424865349C9FFD8C5E6B551895B55ED246BEDB8FED6D276A6032D8FEA8FDB07AA0187847652043DE3C10EA99DED4EA69A1066C14F5B202A2B15A9CEE5D6C073E759917FA30B71F7938D14711E4EA2151C6714CC1BA6FF8593A9961BB3706A7BA6E296FA08F2EF5FD813CD7EC3D6556B80A1C149831E002FBD98B0C3BBCCB681AA3237EBA288FC374C6E272377A8478507CFDCCB6FD4218FFEED286CF071401A0C7D94B23655D3222823D591609103ABA299FC9C135A12F1520B1A12318E7D3330BC3F5C8E6A9EEF0DB722F967A42F3A798F697AFC1FDDA7E08665ADCA283D9BD8AFCEFA3701C4FACFCEA36BB64A4E78FE901EB40A17D435FB9204C2C3DD6DC8AE9E6D4A894703030493F877F9C3C9954AB3C4E8BDBF76A6D60B607BB4A9EA22443A6FFAF6D641A477053ECBA67EA79A6AF0A16241A6CBB7649266695248BCF445E1CF4B131D052786E45C7869E8475D877E5ADE5F6B73BDFCA355F1E1123435FBFF4D4B21E41E0E70061DE8F770E1A50C79C449CF6301437BC78544D4FE99C9235260B2EF4F2AF70E0089218A954DD74BD0E90F243BB38DCA538E765F4BB426E0EF7F1280736CD830C1AA63A8DBA101BC0D5E831F941459D8991F1383B27E691902765EFF317AEAB47B18F08F136901273EA59D583179FE0656B14F15C494CD3E1696BF34C5AB0AE5E086B7C09CE77BC049EC522D4DB7A79AE6E595CD28766FC95A108211B4BFF3C5EC844C8B0F93F55E97BAED9336F13FFAD5708F005A1FC089DFBB74030AF7EB43D9D4C3A52AE26DE2AAC252777D0C96DF7743BB1F69845877EC09344AFBA503C976CCEFC4BA92554BDD0D5C3011B4DD7922B3308D651612FF4A803EA14DAC44697EB78DC76132A4AD33F6C702377F9A611DE8BAB8156429C65972FA59C439BF3CFD8A3916DBB5555A1C8C535516390B4DE591DB5A1A60576A1B07300F16CFF9A41A2FA6EBAEAA96999DD9A4BA0A57DF59F0912DD2F2A1D8554281AD094EBB29819AC682DE772504E697D647144F8A1BC68F7DA2BC4C5D5BCFD8B5A5578A0B433D53B2D992CECFB9FAFBA539A7C34664794EAA5634E0B467E7E5B60CA7C773F91B8C693F7358511270413564C889D1E8A3352938D8C026F53C384A780FFADE864C65974FB9B0BF5714E06BBFAC3BA23E6B4AE0FB804BE231A999430EA3D7098A60E83913875F19650E466F857EA162D744002B0A9BD26D8E446A3BA0461DD0B3DF51B65799578EFC5B2A4C02DE357BE921AD941B821263FA8A65F3F1DA06314F68AF374B523FC821A349ACC8A5A1CC6F25C84DAE143B4105F82BEA670E1B90A9A7EA0C88C363E0A281901BDDCC95494876C9024E2A83BDC50C0C1ED441C2D39F6F84DF041F463E6ABB79E01BD307122D700C9F05F79BD7AB38F245B068AA1B2B4B7BB05AEC88E0F433E82E44F1635DD94DB45C217C529C7A089E2C083480A6C7D94E53378E0F54436C33E1C560EA35D1F94CB948FA0CFEDEE0EC86B19664AD9F71AFE45E1D76289D66771857366BC3085A82190ED601C6C098FE396A831E59A64CA4B33BDC824DDB5FE352E8743BF7F3DB96EB68099EDDCB8BB4942F791B0DB10E12A3817A5804D84C50B34747687E6D1ABDCE737CB37A201C49A0E7195D3BBA3976C98CCE3A5517146AF80D236C1569E10698D6F7474E8F57A7BB0152846BA953906913153225DE94E5E614BF9C189A2375217402D1E367C1AE300043631A905594F77FCC929AFF2DEEB81D6463D9A336798BA10DB25484AAB2E2B524CD8CE8F897B819484487092A3C6595F5C508A5630CA57A5D5D2A0C38BFD74459EFA380D5714ED0E6E8583CCA9B4EEF45547D69D965804A131FEF72D210C2F698D24C96F35F2271C3075702E39B0CC3D7C2B876EB7ABFE903549057A13570B5C27DCAC9C7C817F657F3152EA3B83DE8CF80BF7F32E4FEE574205DE20FBC5003F5A4EE7FE481FE08BF1190ADEC145B3EAA3ADE14588863364DC56AD0BB71AB0F2054E63B9DB14A315210ACCD6CF4E773DD66E3CF9F06A512B50AB8F8012E50E9E8A0D22579C3D06EB03A98A272D1FBA4E8C9E7097957A71CA98E7825494949FFE56918B033CDAD8D2AB8E473B2659CAB2F16675036670A3DC32FA8799D8296A8B561B5B389EBE0BEC2AD84000C2BEAB96C1375FE90C45187C3C2B7EA362FE5EC8A61BD4AFED180E5CF1042F4A1B8C9B1C895B66167E383C17BF24CC1C57ACCA1749AD3F283E365B08B8931955F921B6B394706F358E68BC0D6BCC6BE2E014F4716F3EFE7B57D52D1188B1B687AD87B1595708CF51AA907D6D26B0A4C0A2BF653221AD2F45D1567D8102ECDFC76ED97EFC55E9D028F7A2A7A6146A1E817AB7A62DB2E7504ED390502DE61453A56A5E82455D97468ED45D5FCD90631EEB736B90D29210AF7B3F80E7C9528682A33811170C93C5E70D5C72DDC7D24CC03B829D637CC234B72FAB977AB5456A379BF294D905FA0A6D916213306B0C2D2685CC9B65BE53D3EA4B6685EA072E2A67120DAB1EF267072BEEA7D5CABBC5D4567B538234CF8B5CF8F52B04E173B8AF111E38E7CD41B302377D7D88A13CC233C7E8A11D95EB39933D32961ECB5DB0B711D6A943BDBBFCB62964CFCCC0F1C47390565F6C4036730B4E81D76C04577B466798A336BCA39018AA374E02E850EEE20D494D33B576108F3EF3175298E9E7624A907ECAD832CDE6114EF5B559263DE40AB5AC114137AFD367093BB745D75F3538BF91E97AB9D6B2B8A064E08FD30F5AA4AA4092C413D402EFA7E9C3B15B0EDBC4C1FAF296DC6719CB38A9DAD61A2B28C08B4FB681F3D9D5E09DA6BD3F69003DFAB8C73DC5EC81CDD9FDD25DE19B308FFD4EBF87370D2921063E7DC48E779B5F1B2BD82A4A4FAAD6F22D6C64379D564938F683579AABE88B7658BDE49650772C6510DF555D0C8BC0D8FF44A1B9237AEF8F9D3C25545FEB258376338B188B4356F8D647D2CC24D8B0156070865C2E43B770E589A036153ECDDA425006C76EC9A6B7AAED74ACA26EFCA60BAF0B601D4ED3B0F81B847557AD77C14AB01E1D8FFD3BE68C955A376AF2573F9F7CCEA1429A61D19EBD1BD15B672BACDDC76F22A3BBB2A4EC0674C0154C542EF0A3D070458E209420865945B2C87013672E8F760A5C6F2B7B0A403399D0A063B81D28DAD76B8EBF5BB03A006503EA34402906774C9503DDA4D78CAE11A81F4BAD2825C0F4E5278C39CC7CBF4316CC887796719EA660E08128C6F7C7F198A540D33A6825B0E806B4BC235DCC5B3A44AC9498B39A528EA433112F210B8728B274E1029CEFCF12830B53F941A84ED741EB0884FC3DFF3060CC778F38DEC5CB10B177CF73CBAAAFD7D73ED7F5CD47A4EF2228210EC8449DAEBD769D93843682EF4BAD23E3825BC81F9F15A490902C6392351275D3BC7C8E550F2D5C3705F377D4C2C113AA23BEDC7BF209EB3D80E698EED9A24A50705653EE02FB199A9A77EEA24DF2E4DD8ED1CA17396334C4EF723EF0C76910F375A31CC0F04384ADB4D163123DEB4BE93D0767B76EDDC700D68563EC320D5540B2B2CAA4213A3C290FD19C6880133FB6C82B5988B880C55F3B76F716C481C10B996A63A0CB15ADFE2A8119154F0CC888A16004DB825586A8D10E90F5D2F4B2F33478EE2BC678E0827845E8052AC52DB6DB1EA68980BB36B16607F1A9709F958ECA97BD4AEA4292C4D25FF781574E79400A0144902FC8DB2C23C3457ABCD179DCB1A0EF6F9BC7485F0170DC03452671E6EDD45AAEE9C1CB56F6240597DA6E7E075FE2E2ADE4BD3E5DDD3FCE08C2ED982DE5E5B0CCD5148E7DB4C5338518C1EDA36BE1AA1AD69FC949AFCACA5146B165B8DE0096F8A178C55AEEA19D1A5E72AF68195073E5C70C18AB50461ED8C92BA4E43CB41DAC5121DE6084B8E72FE24290EE2CF29AE8D575F3A649CBF22229259C20ED3DEF8896C757C5DEB9E8255415DE28B8007E1A096F081A54F9C51CC247067D70EDAAC29E3984534F8A434AAC57B41B916023157525C19CE498A6ABD85EA3869E3DFCB769C07EAE418FD56467B64F762F80B0DB1E6289996C0EE599A3C90D3FD68593E0DC53FF1DF7CFA6120D9E060DA6C39471545275B985D4F4A948CBC8176B1AD5586ABF279E6326F42007DD5D5547A50F6DD3CB31ECA2FB174B377034B3F4941904662144A08E942280EA72C8742CE4BC8BD8FDF31D0912764709AE9CACFAFF64273EFB9629C8A22311E074A210A97402CFD27AEAD871E725F010546F3312E4FAA102674A16381BE205D10F177477637ADA1679766F52CD54ACF9093F66BB68BBA5A4C51F50895E30B75BEA4BB34AE1A4902683937464BF9B30ADDAB245117B36ECDB4686B8863D1CE146A4695AB861934194B05EBAE540EA2E0F4D7A919D145DFC3F07355974E9D0E72B08917A8058BC90905D1C2A1DFC916DE4DBEA69629A91CA9686633328C11EE25199DE6730E129474E38199B16F860D4167CF84CBF244F036510D551E5F2421599226265DAEB98AA64EC90F707CC6B2E25917DFFE476FB677AA9B74F6F28DE2F1FE45D59FE7769E9456C81F1C0BECDEB72C34BE80C64A0DA7AB9A4DF25EA25E31235AD3922AB7701DD7B55377AE615740BF61FFB12345EB91AB9D1DA607437938CAC76EC3AEBFF837FD7E40DD41DF663B585B5064C535C413E2C2F2C17D94BD077F4FAC890489874474CFE6989562C0DD416D3CF0257C28E4EBA22C0D2A53ED8B06E0763A1F251351FFD06F1A214DBA6F8DF41FCF870369FDD6CC668D014E9052D534593BBEA2CDF43BE12614E469201431AEB7CEBEBB2574F05B9FF1F03A443C5ED23962E40D11C50D1A47DC1E9D3B8A4DE77FB2AF37A347D22F0CD43BD97D82A51EE916D00436018763DB9DBFB4AF020AA34800231F80C9C08C85287E875DB97FA8D0B392509EF400076C84E503F790AF571A43DDBE9A89D63B7FF09A1868772364008C9575DCFE829FBCAF17825447A14B1BD758D0AB860506675DCBB35CAFFE95D24291E32899B4B1C661D2122FDD61611EAC3B79C85AD52ACF7D1C46D59C3E1AE2C56893734DF302AB8A39A2B88F4664444DB4A02C3093152185B7E521F93F453B642221FFC864C48AA0C9829EE6607E641A566691341DDAB4821A1495124828F7A1C1B213B0722BBC141670BF2E7E17D0D8ECADC6F254D15942F22C97FDCF563403FE6B43F965EC1985AC0F2489BA8612354890D446D2C73BE0AF14BAAC3246D485987462C8526813700716E538024E7CB4F3678D4CAA7D0617E4318ED320F1CE22980DDBAFCF12B317766D560D55F25F8C1603933DF3F25C3AB99588B3B16AAF6F9E7506528C26DF309911903E6838152E0C723E189553DF533F8EA4DB2ED6301D02EBA3EDF34A089A1431F6BFA36F6193CF689EBD9DC6F32D2EF88D124AE0624CD99490FCB1ECB9BEC83CF2B3C9BEDC9B4BB329258E3D2C9A84428816E910C142EECD70476418F96CC0DE5C7BE7E5E16D547C5306C7E32EAF3548F0957D5F3ABDA684BF6D78BE48A485EB86F99DC117720C047F46EA1E64F7677B6B378B4BC16C75F5CF23D3B46A8AF0923D9B30CD68571BE49305C37A4C1E63D821C98493F32FC090897A4EA985A84286A4B8D038C6BC9318E51147619D5E80192DCF9F5A126316B74F7D7FBC717DA532C37E87F7D14B7004EBB73053C292A07F7CA2421A937FFA5353BC837786122491A9789B5B1F301BFBFF5E8710FFD371768A03D905A3FA8018BC4DEB039C2C4FF828F0FF855AC37D62EE35A1F5A3391E5BED826F77451C15361816501E16AF9C543A6CABC8CDB472F941E1533ADD5230DFC3DBDAAECD6EBAAA2134C117FC0B74F81842745135EDA7B7A10D7C8B6FA90B5192FE8F9C1CDDDF2CC9628689A38A5EDD032509683904D2183DC6CCDA2B85B8903C244B21922E22AB74E442056B33933496E5C14932805241C311DA2F511C3EF253F344B14783EB50645E4CC8E5CF85988A58F9F755B8E424358A47C32B953C563F20CE8FD661FF0C8ED19270259C5FB3E9B08B8A635749397B73C7D9C7CBCA49832391AF7A37DD73D43C8FA6A07568AF2A5E33210FC00F9F984A93CD50DA6F91EF031CC844DAD408CD14AF4464566B2DDAA3CED6EADF4E642252004F66EFC9802DAC13039328C4AE170F6353EAB75505BFDDA98D61E534F23DEC6874FD5BBDB349ACAB40FEF21B35C6A3A606C8983F34B374EC43C5D4CB8D78C346BBDBC309EDCF4BDF3DF96AA641CA4999C09DD106CD4CAE332A82930419A089E153C45C6B4DD06F0CA8F031D0FB60135D124FA78DB3E7441100175CE31B3E5A159521FE7747F6588534B6C09D93AFD2DAFFB940C523786CE9C7ABF215546C92AC2F7ACD52436F86A5888482B66558F61278094D7AB73998645AEC3DFA8C42A47F7687B8FED6EDFC3F82ADB5BA717A6E7FD4B55553A3F5622F87F8441AB1C6B23D7E304277DF1F25EC53C3B8AF42AA256F1BB06250A079257AB64281C5D7406145E243869E3736344715BEDD19CC7D59FCD961BBDB8AE9D9E8398C70DD5F16B5536245A0044BCF359124C810630CAFDD51A457B23A6A296B6F94F94FFD723D2A2576A486BFFB73D6A6182BC5105D51793AACA5BA9232411BDBF983DF76FB61002B222BCB970DD384ED14FE816BB8063557C3E0408D0E746CE07DF5C3A5492F9485BFD1A4E7F6CAA6D9A2F074EC3EC59402B688476E7E85E7A11EA6697245114575F27388B3D465763386E46C3A208D64B33D0764D6EDDD83BF307CFF7FB928EF99F742F78E5D4AA368968F8513AEDAE0279F1B8AA3F6AC8E722331F53622071A2D83C7869681A7C137576D4D20F427FC2460E664EA6503D6879E9DE6208F4DBF7F4D28402A56F304DCC0A7E21FE46AECA4ECA90AFFE04A94CB43D3073A4FCB9B53ADA5986BF33989BEEEF174AE118496CD98CFD7CBF54373111BB3A66CB22F44D11C2946E43716D30DC97DEFD05B288493CC56B2DE18457CA894931CB306460BAF84510C0C4753D34873607CC029DECBA131A37855F618B05BD4125C0DD84D5BC859D2F8271F06E8DBBB955AE5B790D623F0AECA36629EE87BBB92B08A7B68620895E63982AE8C290F33EE9A8E19775580B00D87D2D79022B27C5986109F4CA02B66A4152030558D5ECBE0DAAD033F82BF9C86BF54D43C3D5395E2557CF92E09DE0E446D95F89810A292E37A4D92DFCFDF4032C9563E22CB57D9CF24E5312B1B2BAC071EB20C1FA2882069A92D1AC19AE67B8D2A840D3C34A606F60192C273FD253F0DE9A2E1ADCC06064B23292DE3713C002DA40EB3139F91D32DEAD8D9F07D44154D2F67A502CD51AB2262B4AA00D93F65574C0D578E50606E6E20087531224FE372874B228B2671EC6B344FCC67DAD0C1B48690CE21870165C379FDE8D2352DCA9CB25AEC7460887EF7E500C6725154E000AF0C379547692D90C4CBCA78011DC71E32B2A23FC550A0F1BBF7DDBB23A398954D8DCD255B44D6B0DBCCF12CC9B980B14BFCD0832DD74D8EAFCD0806108BB43181CA6E4FE4039E4AAFD8D7C256DB131AD954BE164D3870505B54C20EEC74BA7A510FAC251386D697CB826C08AED1493D2A0006949C8DD711278EC798E9567B38EDE12E84BF0EBEE3C391778ADA2AD4FC8E5AF1DF7B2ACFE3ECC75F5BC95109B21829A835C003464EF564528DC0A6BECF8D939C9F7409F867988DC09DA5C70F96146DE1C8FFA4F54B7FA210
+sk = 4053237912EA281C51C4456A5096589EC9D20219651E00F9704178F0CF84F9AEB6CF9B91058293C99CED29F30BD7B76DFA03877BB9E6AAFC179DC638C8292EEDFA122AB384A60D18DA42AA426D7B08031564B1AD45FDD32956F77EDFBBDC012894A76CEBED542A941A60DB8885DA44CBDF284329CE3D59576E9DD444F16EE47062C95687A29F76815E884BB1CFABB86E49C0836818A3AB9D59D0ADCC6BD3E6C3ECF8478111BAC9D35E3754390DBBDC30D8DB134B5C28044B1C66D7238FC9FE354224466884A660268EB2717417B0D6F523C7D54DBC178080A88CC4113093F22FFA6BA5850F83C0B965FAD1581227C37C8C48C614BF81771867EC49427E82AFF14B352243F92610D8BC9677167ACF5FE133806D8F807480B33A1EDDC41D5E26B902EC4CC5B47A232B51C2843166FB2C499D3A3EE402F4CC950E6A190D016AAAC955071F5B9E982B80D6A21E22A5AB047C3566EBC32EC36DF0C738E269820A889C2A48DFE70BD7EFA6DB069436C09289BFE321219771C03ABD46014CF48B1F033BDF9D71FF5FA80566AA63CC8A1703B34216F81E22150647229F649C3072F323BC99D051E3940CCB76F71BBA1492C599D3FDBEAFA5E0BA7A7D5847F90D31108CB722A2A9823FC2FFD3DDA07CCA6869E13CACCDF1F19992BEF53901ED5367DD457F98982AF673574B129E3485F64D80C18CDDE3F81272E53504081F6417D5EAFA78E2ACB67956CD9582B0F96B72F8564414FEB6C13B6E86626F96E83AA93FC231E012D3F97259582A784961DACC44A5322EB76F3AB8F2E66BEE375B94C2088480A9B8B146D615E215D6F60E95C97E2D0884B77739C6CD853C1A0D7A0EA0C00E34350AC4407FC1018D7704AE5EA8509F2EB4F31B262E6774DB45BBE008C5C7218C30133238250BD3BC12CA990B99C571345D9A271BCBF775534D072717E1DFEBC4AE6CC86E81412414AA1576DFC2E435C7240D133F0C1FEAE87574AA6DAD6A78CBFAE3ED13C9927C6D23ACC2D55070A91F9B8B441FC6AA1A46CD292F0B914BE3A4F87AC9742056C7099AC5D4E3C4A79B164653B0A00E30B3AEF98C7B50AE93B79C6D06AF8C2DB47D2F97C0A50FAB23BB0F461BAD0E791EA3201850EF2B9BA8CDB0F8881AC95D9C34A0CCB63D85F31EEF3A3042BBC6CF97350456F4E791C0BC3B767751856202A719C066669C2710954857EA7FEA97C7191ACF2E5B5E6C8F57A8B3802CA3E8B97D0C52DB3B7B392D2A80B7883CC9C281BD87F7E4FFC6998E6D575B077D443EC4C51E3BE443741D364EACB1DEEE36F82E632091E384720BE26658337B5F108192FEA007115D69E57221DC4187D1157B4C72A2DA3406F384FACE5512C0878A086C1DD8B5D3B985CB75CB0C553CE5FCFCEB4271319397630166A2ADC96EAD14301EFD852DAD993D4D63757B27DB3DC9DA5D5869B90BA01FF0F3B045E7AA06C25F682FFEE3724ED8B80170270283DE04BB2FBB5F22CAEA06EFAED21DE0124451478A414D57E9561D905D891BF794E3823BB6336AF8A6A10AE3940C42A2FCDEDA2A2B69C223E3CBFC6D87211ED4D1137F122A00BD713F1EA50A9D9C4998313BB391CFD8E626983F306EBB92E2474106F0D8B5AD7ED0982EF4B2F80189A7A18E9B47704388BCAE76DA6CA3E5404AB617AE9913CE820F656EDF967D436CE5678A60839C3A8C8942AC52E402769C7C01920B5B1FF8A3E6160AA74B6163A4F0520478A1B70A32C71B4B7A4519AFEFC44A70E34706D57A14F6AC06DF09923800B63091FD41CF2CF7ACD2333D75692E751268E8DA2508F2BA8D889F684F248D259835EA274AA26AFABD6DBB714AF55D532D5B8345A58A3DBB8FB431EB0672D223A49DE20910CF27FCA95FEF5FD6780452787478410AAABC4C839F0A95D85BD2904058483C1F789A6E8ABEEF638225F2BEE9E228F76C6D2B5CB77C74E3EA35866760CE032DD95E17C6AA201E1CE2C230C4F66AA0DF96A0E82295745493D31F1103EE48A7D0B6296676F8247EBD02240784EFD81A8BAA2C7A0FDE2375821E477EFDF9CE41C3B7FC87ACFA6701B3EBFAFB88FA84761E72151C183E2D148F4106B2C3A855B1F36C28905A9D93251D6F9544CA17581DA9A1E5BEA14FD9B4DFE9D58A7975866F737CA16A93E1742BC6C1A84DACD9C5F80FE449D3382CFB8E461946403B5E484653C725CE9050F7128FD4AA53FA2AE0056CD0E5A353117CA6F815B450482BE2717010DFABA09F9F4DD998BC5035F7BCBA4A3B1F62F55C158185EE529BB985710CB176673A2E56501F65A3B92D05B8C8CED55C3B32C7EA6E83EB6C251CD24362E0541F19357726188A0E61F89209E821EC4A9003A20194D76F7AEF6B3121DB699931150AAAAEA8D1943726A567970D24BA5E69F3684B4CE75C125AFDFC58E9CFBAEB00146D5BF70C7AF01E43F633B5F457C9C6F8061434D6C3BDA84894E589FA9740FEFBA92DCED7E8537191482DF487D97F833DACBDC64AFCC7C33DF8B622F035CBA42CC4AB79E16A5ED2967A93703EED0761BACC87C5C814F55A3636404A2A1912D442EDBE2B9424865349C9FFD8C5E6B551895B55ED246BEDB8FED6D276A6032D8FEA8FDB07AA0187847652043DE3C10EA99DED4EA69A1066C14F5B202A2B15A9CEE5D6C073E759917FA30B71F7938D14711E4EA2151C6714CC1BA6FF8593A9961BB3706A7BA6E296FA08F2EF5FD813CD7EC3D6556B80A1C149831E002FBD98B0C3BBCCB681AA3237EBA288FC374C6E272377A8478507CFDCCB6FD4218FFEED286CF071401A0C7D94B23655D3222823D591609103ABA299FC9C135A12F1520B1A12318E7D3330BC3F5C8E6A9EEF0DB722F967A42F3A798F697AFC1FDDA7E08665ADCA283D9BD8AFCEFA3701C4FACFCEA36BB64A4E78FE901EB40A17D435FB9204C2C3DD6DC8AE9E6D4A894703030493F877F9C3C9954AB3C4E8BDBF76A6D60B607BB4A9EA22443A6FFAF6D641A477053ECBA67EA79A6AF0A16241A6CBB7649266695248BCF445E1CF4B131D052786E45C7869E8475D877E5ADE5F6B73BDFCA355F1E1123435FBFF4D4B21E41E0E70061DE8F770E1A50C79C449CF6301437BC78544D4FE99C9235260B2EF4F2AF70E0089218A954DD74BD0E90F243BB38DCA538E765F4BB426E0EF7F1280736CD830C1AA63A8DBA101BC0D5E831F941459D8991F1383B27E691902765EFF317AEAB47B18F08F136901273EA59D583179FE0656B14F15C494CD3E1696BF34C5AB0AE5E086B7C09CE77BC049EC522D4DB7A79AE6E595CD28766FC95A108211B4BFF3C5EC844C8B0F93F55E97BAED9336F13FFAD5708F005A1FC089DFBB74030AF7EB43D9D4C3A52AE26DE2AAC252777D0C96DF7743BB1F69845877EC09344AFBA503C976CCEFC4BA92554BDD0D5C3011B4DD7922B3308D651612FF4A803EA14DAC44697EB78DC76132A4AD33F6C702377F9A611DE8BAB8156429C65972FA59C439BF3CFD8A3916DBB5555A1C8C535516390B4DE591DB5A1A60576A1B07300F16CFF9A41A2FA6EBAEAA96999DD9A4BA0A57DF59F0912DD2F2A1D8554281AD094EBB29819AC682DE772504E697D647144F8A1BC68F7DA2BC4C5D5BCFD8B5A5578A0B433D53B2D992CECFB9FAFBA539A7C34664794EAA5634E0B467E7E5B60CA7C773F91B8C693F7358511270413564C889D1E8A3352938D8C026F53C384A780FFADE864C65974FB9B0BF5714E06BBFAC3BA23E6B4AE0FB804BE231A999430EA3D7098A60E83913875F19650E466F857EA162D744002B0A9BD26D8E446A3BA0461DD0B3DF51B65799578EFC5B2A4C02DE357BE921AD941B821263FA8A65F3F1DA06314F68AF374B523FC821A349ACC8A5A1CC6F25C84DAE143B4105F82BEA670E1B90A9A7EA0C88C363E0A281901BDDCC95494876C9024E2A83BDC50C0C1ED441C2D39F6F84DF041F463E6ABB79E01BD307122D700C9F05F79BD7AB38F245B068AA1B2B4B7BB05AEC88E0F433E82E44F1635DD94DB45C217C529C7A089E2C083480A6C7D94E53378E0F54436C33E1C560EA35D1F94CB948FA0CFEDEE0EC86B19664AD9F71AFE45E1D76289D66771857366BC3085A82190ED601C6C098FE396A831E59A64CA4B33BDC824DDB5FE352E8743BF7F3DB96EB68099EDDCB8BB4942F791B0DB10E12A3817A5804D84C50B34747687E6D1ABDCE737CB37A201C49A0E7195D3BBA3976C98CCE3A5517146AF80D236C1569E10698D6F7474E8F57A7BB0152846BA953906913153225DE94E5E614BF9C189A2375217402D1E367C1AE300043631A905594F77FCC929AFF2DEEB81D6463D9A336798BA10DB25484AAB2E2B524CD8CE8F897B819484487092A3C6595F5C508A5630CA57A5D5D2A0C38BFD74459EFA380D5714ED0E6E8583CCA9B4EEF45547D69D965804A131FEF72D210C2F698D24C96F35F2271C3075702E39B0CC3D7C2B876EB7ABFE903549057A13570B5C27DCAC9C7C817F657F3152EA3B83DE8CF80BF7F32E4FEE574205DE20FBC5003F5A4EE7FE481FE08BF1190ADEC145B3EAA3ADE14588863364DC56AD0BB71AB0F2054E63B9DB14A315210ACCD6CF4E773DD66E3CF9F06A512B50AB8F8012E50E9E8A0D22579C3D06EB03A98A272D1FBA4E8C9E7097957A71CA98E7825494949FFE56918B033CDAD8D2AB8E473B2659CAB2F16675036670A3DC32FA8799D8296A8B561B5B389EBE0BEC2AD84000C2BEAB96C1375FE90C45187C3C2B7EA362FE5EC8A61BD4AFED180E5CF1042F4A1B8C9B1C895B66167E383C17BF24CC1C57ACCA1749AD3F283E365B08B8931955F921B6B394706F358E68BC0D6BCC6BE2E014F4716F3EFE7B57D52D1188B1B687AD87B1595708CF51AA907D6D26B0A4C0A2BF653221AD2F45D1567D8102ECDFC76ED97EFC55E9D028F7A2A7A6146A1E817AB7A62DB2E7504ED390502DE61453A56A5E82455D97468ED45D5FCD90631EEB736B90D29210AF7B3F80E7C9528682A33811170C93C5E70D5C72DDC7D24CC03B829D637CC234B72FAB977AB5456A379BF294D905FA0A6D916213306B0C2D2685CC9B65BE53D3EA4B6685EA072E2A67120DAB1EF267072BEEA7D5CABBC5D4567B538234CF8B5CF8F52B04E173B8AF111E38E7CD41B302377D7D88A13CC233C7E8A11D95EB39933D32961ECB5DB0B711D6A943BDBBFCB62964CFCCC0F1C47390565F6C4036730B4E81D76C04577B466798A336BCA39018AA374E02E850EEE20D494D33B576108F3EF3175298E9E7624A907ECAD832CDE6114EF5B559263DE40AB5AC114137AFD367093BB745D75F3538BF91E97AB9D6B2B8A064E08FD30F5AA4AA4092C413D402EFA7E9C3B15B0EDBC4C1FAF296DC6719CB38A9DAD61A2B28C08B4FB681F3D9D5E09DA6BD3F69003DFAB8C73DC5EC81CDD9FDD25DE19B308FFD4EBF87370D2921063E7DC48E779B5F1B2BD82A4A4FAAD6F22D6C64379D564938F683579AABE88B7658BDE49650772C6510DF555D0C8BC0D8FF44A1B9237AEF8F9D3C25545FEB258376338B188B4356F8D647D2CC24D8B0156070865C2E43B770E589A036153ECDDA425006C76EC9A6B7AAED74ACA26EFCA60BAF0B601D4ED3B0F81B847557AD77C14AB01E1D8FFD3BE68C955A376AF2573F9F7CCEA1429A61D19EBD1BD15B672BACDDC76F22A3BBB2A4EC0674C0154C542EF0A3D070458E209420865945B2C87013672E8F760A5C6F2B7B0A403399D0A063B81D28DAD76B8EBF5BB03A006503EA34402906774C9503DDA4D78CAE11A81F4BAD2825C0F4E5278C39CC7CBF4316CC887796719EA660E08128C6F7C7F198A540D33A6825B0E806B4BC235DCC5B3A44AC9498B39A528EA433112F210B8728B274E1029CEFCF12830B53F941A84ED741EB0884FC3DFF3060CC778F38DEC5CB10B177CF73CBAAAFD7D73ED7F5CD47A4EF2228210EC8449DAEBD769D93843682EF4BAD23E3825BC81F9F15A490902C6392351275D3BC7C8E550F2D5C3705F377D4C2C113AA23BEDC7BF209EB3D80E698EED9A24A50705653EE02FB199A9A77EEA24DF2E4DD8ED1CA17396334C4EF723EF0C76910F375A31CC0F04384ADB4D163123DEB4BE93D0767B76EDDC700D68563EC320D5540B2B2CAA4213A3C290FD19C6880133FB6C82B5988B880C55F3B76F716C481C10B996A63A0CB15ADFE2A8119154F0CC888A16004DB825586A8D10E90F5D2F4B2F33478EE2BC678E0827845E8052AC52DB6DB1EA68980BB36B16607F1A9709F958ECA97BD4AEA4292C4D25FF781574E79400A0144902FC8DB2C23C3457ABCD179DCB1A0EF6F9BC7485F0170DC03452671E6EDD45AAEE9C1CB56F6240597DA6E7E075FE2E2ADE4BD3E5DDD3FCE08C2ED982DE5E5B0CCD5148E7DB4C5338518C1EDA36BE1AA1AD69FC949AFCACA5146B165B8DE0096F8A178C55AEEA19D1A5E72AF68195073E5C70C18AB50461ED8C92BA4E43CB41DAC5121DE6084B8E72FE24290EE2CF29AE8D575F3A649CBF22229259C20ED3DEF8896C757C5DEB9E8255415DE28B8007E1A096F081A54F9C51CC247067D70EDAAC29E3984534F8A434AAC57B41B916023157525C19CE498A6ABD85EA3869E3DFCB769C07EAE418FD56467B64F762F80B0DB1E6289996C0EE599A3C90D3FD68593E0DC53FF1DF7CFA6120D9E060DA6C39471545275B985D4F4A948CBC8176B1AD5586ABF279E6326F42007DD5D5547A50F6DD3CB31ECA2FB174B377034B3F4941904662144A08E942280EA72C8742CE4BC8BD8FDF31D0912764709AE9CACFAFF64273EFB9629C8A22311E074A210A97402CFD27AEAD871E725F010546F3312E4FAA102674A16381BE205D10F177477637ADA1679766F52CD54ACF9093F66BB68BBA5A4C51F50895E30B75BEA4BB34AE1A4902683937464BF9B30ADDAB245117B36ECDB4686B8863D1CE146A4695AB861934194B05EBAE540EA2E0F4D7A919D145DFC3F07355974E9D0E72B08917A8058BC90905D1C2A1DFC916DE4DBEA69629A91CA9686633328C11EE25199DE6730E129474E38199B16F860D4167CF84CBF244F036510D551E5F2421599226265DAEB98AA64EC90F707CC6B2E25917DFFE476FB677AA9B74F6F28DE2F1FE45D59FE7769E9456C81F1C0BECDEB72C34BE80C64A0DA7AB9A4DF25EA25E31235AD3922AB7701DD7B55377AE615740BF61FFB12345EB91AB9D1DA607437938CAC76EC3AEBFF837FD7E40DD41DF663B585B5064C535C413E2C2F2C17D94BD077F4FAC890489874474CFE6989562C0DD416D3CF0257C28E4EBA22C0D2A53ED8B06E0763A1F251351FFD06F1A214DBA6F8DF41FCF870369FDD6CC668D014E9052D534593BBEA2CDF43BE12614E469201431AEB7CEBEBB2574F05B9FF1F03A443C5ED23962E40D11C50D1A47DC1E9D3B8A4DE77FB2AF37A347D22F0CD43BD97D82A51EE916D00436018763DB9DBFB4AF020AA34800231F80C9C08C85287E875DB97FA8D0B392509EF400076C84E503F790AF571A43DDBE9A89D63B7FF09A1868772364008C9575DCFE829FBCAF17825447A14B1BD758D0AB860506675DCBB35CAFFE95D24291E32899B4B1C661D2122FDD61611EAC3B79C85AD52ACF7D1C46D59C3E1AE2C56893734DF302AB8A39A2B88F4664444DB4A02C3093152185B7E521F93F453B642221FFC864C48AA0C9829EE6607E641A566691341DDAB4821A1495124828F7A1C1B213B0722BBC141670BF2E7E17D0D8ECADC6F254D15942F22C97FDCF563403FE6B43F965EC1985AC0F2489BA8612354890D446D2C73BE0AF14BAAC3246D485987462C8526813700716E538024E7CB4F3678D4CAA7D0617E4318ED320F1CE22980DDBAFCF12B317766D560D55F25F8C1603933DF3F25C3AB99588B3B16AAF6F9E7506528C26DF309911903E6838152E0C723E189553DF533F8EA4DB2ED6301D02EBA3EDF34A089A1431F6BFA36F6193CF689EBD9DC6F32D2EF88D124AE0624CD99490FCB1ECB9BEC83CF2B3C9BEDC9B4BB329258E3D2C9A84428816E910C142EECD70476418F96CC0DE5C7BE7E5E16D547C5306C7E32EAF3548F0957D5F3ABDA684BF6D78BE48A485EB86F99DC117720C047F46EA1E64F7677B6B378B4BC16C75F5CF23D3B46A8AF0923D9B30CD68571BE49305C37A4C1E63D821C98493F32FC090897A4EA985A84286A4B8D038C6BC9318E51147619D5E80192DCF9F5A126316B74F7D7FBC717DA532C37E87F7D14B7004EBB73053C292A07F7CA2421A937FFA5353BC837786122491A9789B5B1F301BFBFF5E8710FFD371768A03D905A3FA8018BC4DEB039C2C4FF828F0FF855AC37D62EE35A1F5A3391E5BED826F77451C15361816501E16AF9C543A6CABC8CDB472F941E1533ADD5230DFC3DBDAAECD6EBAAA2134C117FC0B74F81842745135EDA7B7A10D7C8B6FA90B5192FE8F9C1CDDDF2CC9628689A38A5EDD032509683904D2183DC6CCDA2B85B8903C244B21922E22AB74E442056B33933496E5C14932805241C311DA2F511C3EF253F344B14783EB50645E4CC8E5CF85988A58F9F755B8E424358A47C32B953C563F20CE8FD661FF0C8ED19270259C5FB3E9B08B8A635749397B73C7D9C7CBCA49832391AF7A37DD73D43C8FA6A07568AF2A5E33210FC00F9F984A93CD50DA6F91EF031CC844DAD408CD14AF4464566B2DDAA3CED6EADF4E642252004F66EFC9802DAC13039328C4AE170F6353EAB75505BFDDA98D61E534F23DEC6874FD5BBDB349ACAB40FEF21B35C6A3A606C8983F34B374EC43C5D4CB8D78C346BBDBC309EDCF4BDF3DF96AA641CA4999C09DD106CD4CAE332A82930419A089E153C45C6B4DD06F0CA8F031D0FB60135D124FA78DB3E7441100175CE31B3E5A159521FE7747F6588534B6C09D93AFD2DAFFB940C523786CE9C7ABF215546C92AC2F7ACD52436F86A5888482B66558F61278094D7AB73998645AEC3DFA8C42A47F7687B8FED6EDFC3F82ADB5BA717A6E7FD4B55553A3F5622F87F8441AB1C6B23D7E304277DF1F25EC53C3B8AF42AA256F1BB06250A079257AB64281C5D7406145E243869E3736344715BEDD19CC7D59FCD961BBDB8AE9D9E8398C70DD5F16B5536245A0044BCF359124C810630CAFDD51A457B23A6A296B6F94F94FFD723D2A2576A486BFFB73D6A6182BC5105D51793AACA5BA9232411BDBF983DF76FB61002B222BCB970DD384ED14FE816BB8063557C3E0408D0E746CE07DF5C3A5492F9485BFD1A4E7F6CAA6D9A2F074EC3EC59402B688476E7E85E7A11EA6697245114575F27388B3D465763386E46C3A208D64B33D0764D6EDDD83BF307CFF7FB928EF99F742F78E5D4AA368968F8513AEDAE0279F1B8AA3F6AC8E722331F53622071A2D83C7869681A7C137576D4D20F427FC2460E664EA6503D6879E9DE6208F4DBF7F4D28402A56F304DCC0A7E21FE46AECA4ECA90AFFE04A94CB43D3073A4FCB9B53ADA5986BF33989BEEEF174AE118496CD98CFD7CBF54373111BB3A66CB22F44D11C2946E43716D30DC97DEFD05B288493CC56B2DE18457CA894931CB306460BAF84510C0C4753D34873607CC029DECBA131A37855F618B05BD4125C0DD84D5BC859D2F8271F06E8DBBB955AE5B790D623F0AECA36629EE87BBB92B08A7B68620895E63982AE8C290F33EE9A8E19775580B00D87D2D79022B27C5986109F4CA02B66A4152030558D5ECBE0DAAD033F82BF9C86BF54D43C3D5395E2557CF92E09DE0E446D95F89810A292E37A4D92DFCFDF4032C9563E22CB57D9CF24E5312B1B2BAC071EB20C1FA2882069A92D1AC19AE67B8D2A840D3C34A606F60192C273FD253F0DE9A2E1ADCC06064B23292DE3713C002DA40EB3139F91D32DEAD8D9F07D44154D2F67A502CD51AB2262B4AA00D93F65574C0D578E50606E6E20087531224FE372874B228B2671EC6B344FCC67DAD0C1B48690CE21870165C379FDE8D2352DCA9CB25AEC7460887EF7E500C6725154E000AF0C379547692D90C4CBCA78011DC71E32B2A23FC550A0F1BBF7DDBB23A398954D8DCD255B44D6B0DBCCF12CC9B980B14BFCD0832DD74D8EAFCD0806108BB43181CA6E4FE4039E4AAFD8D7C256DB131AD954BE164D3870505B54C20EEC74BA7A510FAC251386D697CB826C08AED1493D2A0006949C8DD711278EC798E9567B38EDE12E84BF0EBEE3C391778ADA2AD4FC8E5AF1DF7B2ACFE3ECC75F5BC95109B21829A835C003464EF564528DC0A6BECF8D939C9F7409F867988DC09DA5C70F96146DE1C8FFA4F54B7FA210374B10C73F79FA08D0731BE4F21356D191782EB1D10DEEA5929523B3B4D6D97BF397572E7CEAC24CD55009F822EBE800A3231E1E1FA34A924B899B5B85879FD0CEFC0D60050E04C3171859E54BA888D2F670E22EBE926B0B307A65264FBC08F8
+ct = 4D35734CE1A787632FCB6265044FF2A02747FD6BF7073B5ABC37DD3BB0C2C21E89823ABE75AFA6DD2AF344E79116D42FA852B0F27C1AA4FC157C3515CC3411C828591C27694007776A97BEA1125E4A9544FA0EBC6AD0E3D8DF7145C50B352C17FA988986F67D4AA13A68D56CD9F977BAD4B2868D1A42067B0B1E65C3932E49685E0C4482C78E8AA02DCC86E1333CF39A852CC2C277D07878BFC7EA178E0C8A7FD9D3FD060A495B71C697D9D97FAA6D5F3CCDBD5A9E8899C480066863BBFE0C2AE8726784D48F34D8E8D1C41816C28BFBE241046C784BBA9ECCB1E78E361F9DA657CE48C33B5686BBAB7380C6534F09C2453F0D09828AF76324148239AE89285C54CF2F799C2A1E06A55D119BB093F948182E9567D0521C7F10829213F02F5A26667C819DA17405B001C6BFF1016D5377D40018180356821704B88A3F44EEFC891E20BA0939926FF02E91138DF4EFDBBC74B5C60360267DB6750783018D0A43844E126DC46A3D4BACF5F77CC4B089338C8ED16E225B3229BB81348FD2B6ABDDB758493993C1383AD018F99AAC4FDFA321D7DBDD74A9126AD8812DA5C40B52BCFDABF24465B7C21A42336D187BCEC350EB173CFD736F58F4B491CF4734962257864459F29F1384DB996BB21EE0588EAE9CB32FCE579A8BE349E84EDF5315819E02CDE13CA3540E05D6EE13A0296973A1988D303FC77F0583C321771B33FC39251058C9E6FDCCD2EACD2190048533544B9E2A5A505B359B927E52F9A58AF57F4E7A34BE237776E164C4E81AA546A690411227D492C06B08B88F166B17EF7BC12AE6D02BF9B22628B62C7808785C93E41EC33F585F09CCF49F259BE72BB94A102E134FFC18BF5BD240349750625491F7955C39BF84B7A5948AD5B8FE8A5AEEC2C056BBC83D63238C0697171DD3C593E10EE1A41AB81E9BD08970C4B4423A70D4D719D1E5897C91E09497A7A3AEFC24BD8C7B97874C2A4669B8166434EA4F2975F0076DC390C7FB7C2514E91CD8F148C4336B59C196A9F867D66B1356134BFB5AF8A757349827E5FCEC2CC8B72D286286C9E41F65AC0036241F96E09E5AC176E2410B0734DB60ED82F2BD1AC6C299972804BCB742DDE92893285BE5A325E48740E96B1016F50270EBC9D69395B271F07DC30C2DC2A83906751AB0BEF0C26499DB9B0375F0612F4A054C5BB3CB7744815324C5B52A7ED969ED3475672D84A4EC7151596F586F05D99A7DB12FA15D586B79E3B5D15738D67CA7D223130D15ED9892DE3DBF67BE244FFDC948FE9F9B0E285883882E83E9719AA076BB62FC3DECCA44B7DDD8B65E4A6DEDEFC7DB13B77D77758A1D772672CAD78C369B89E584E42A47DDFD2834BAB0C383DC21FBA65ACCC737564D8099DF2747447404B438460FF4F77B4346026560911C6A0E5A53AD148D8E712D13D693676D41B894E335223AF1766806AB850EBFFA94022B0C2768D09FF78B6C5224571AC5009DD3A305260FB589E8B4B84334CB838070695E889A35BAB3024216A9EE6AE3E75B458C5E222245CBE01528646892252FB5F9F553CD21D2395511C832E854CF0C0E6E1DEFF26D30BB669E0C54C0FFBEF6B98A3D98E455E60155AF70E63DBA2141D4295E58CFDC953B7D444EF2641519445EA3F1E52736FDC8BF891481F20D4144E1E1E7FA4137EEDF0CC1EF6159F89B145A5DC652DF69DEFD86BA25857573DD2823D21D768E64A22374DB37D0BD9FBC7045A84642C05B8583551C3A92BAA3D80F7797F4831C6A87ED33BFEC80033DDF3BF3202D87B5A27D44E98D6AC431961941C9CA71CBDC27B55979554BFF9DAD2FDD8B87D90BC6F1198E572187FB00CA05ACFFA6E193058095ABD82E96FE5D0F774C1DD5D17A5544087D0001AC494B6B5E997AF1E1CA2E85EC0CD44A9620AA7BE79E5179361ED31B9A8F377E41209FBA0D889C24A120544CB3B1E051321B7F5F2382801036DAFF203181CDF5594B57B0FDB98BA65876FF55C350009FE1981CA32C12BCB6B3D6831B584828F17C4B2295FE33BE77C6D1B19D0550E5CDDC550179582614BCA8B924A829B06A330480902E070DFFA1518CC4FCFE3CF140F3EB0875C11DE28CF69225B7B437DEB9CECEE94FF7ECC13A180CD515EC8D8BF910A73E98D53607B4888CAD8BEDE55C5DD0A03AFF46EA6D14B3ADA863B322B7B226D6AFB149D9A4C3174010C365B9EED1E5559E8ED8A069484D4CEEB5E8313ABCE78BD371CDFE32CAB9638B7A035F7E70E4B58024A687CD40C6333EB3840A7F9CE1E0453C2740D09A15625824D7002AA82F0B8B54CF452043DC42B6D22FE7A69FE7A72805F752701B9947E431DFD7E6B69902FD949D121746AC671770081B7181E2F747BE4B871A6391EB2DC074FBA05F53E3BE34783EA60A8D696B405E279167CC2A5B4C808233197F0E63A4D2A1801A2CEED366C1FD891E5E858DF967BA376BE672E35ACFDF998063A76C163CBB3BFEAAF570C984643A7639BA010952633E3CD9F33F8C4C4EE2B2571B75878C7DE454BA106691269869FE351DBE454B82F945473FADEA63EB5111ECF52ADB73AA3C48410C59D39E0EF504AD8FFB16671DD222B0BC31F8CB6A58C3BC4C1379DDAD0112ED517EA2F8B5F962E7F4DA58D222B121B2E68D7826031213CFD91860BB0B6B2E963BD912EDD3176C009A949F4D9C674B5717EE0CC298B60267F8DD272C50635B5C5A51E64DDB8A60325A48805F5688AD93B2ACC0278D77C668E01E7C5C6E841258F25879E8780A856F5F8C9BB29E2A54774C770EA7EC96EE4DD300D62EF8B37C1C6A89006A4DF94D9A51CB6A0F24523251583614ECB4238225F8AD1977833456B3CF445BD2A20F6D600F98F93E425F0AA404F1945FB6870685D0A30F1672AB13DA3B319F54E2503B4FB9B03EE138B0A507F6DF5904802AB742E552A6EF1952DAF50AA19DEA0E0C68B15DDD0A9B9CC772FA418F5D7040652B57549A60924E9A49B9D88AE3C2EC0D7798C2CB7995B0FB1BE415A77E6C168EF1809C83A302886C6F24AC89CB1D45A5282438D7A3405F5E24A887154C83BE075724F666C96F5A09AD807F7E17E3966CCA4BFDFA3D6AA70343D560A50FABA9565196D88CCCA9945D90AA9B90AEF520E392CE444B028FA23C171F47F73419310ABD28253ED4B635A7DF0DFDD6AC1CDFF5BF4B7D3F6172BC6EFB5E2CF010EEB996D08EA1BEA7EBA58F0B349AD1FBE400285C6FC7233B03880AE757C9B15619AD09119454C70186DC69F9C775314787F3CA03884ADEE505966077E93ADCD328BA8EECD73D9E6E1E378D528F1E1BE8E4A231F5C519D57D9F61D8678C74B7AC63EB9DC8EFC71B9ABBAAA7F331F2AC8C7EA36BECE692567E82BA3A47BB9A346EDDE69A1C4E8DBD59C3D83FECBDEB9BBE2AF86C1B4E7A4ED52D638D5D9322D91F6B2D3F8CA604EDDBFCC776AD8FBFD8096B948EC9727D7973470A40F48FA95FBDAA6E8F576D21FAD136F928A7BA6C4DF7A516DDA569EF43570CDEEDF4157B452E3A81D21F896EEE704F0A83A1D24686A49C53760BA2A263A86FC939386D49D4173B2797B2F90E50627EACF1AE36F99C7F361415DC64129629ADB5B4CED8B2E6A76D7C3B8A2CF844258EE0C7345316119C3ECAFFFDAE88D96198CD4E07EA6A41545C0E2BC6951FC41182DE74A83DDE8F2F1B3C4502272FCFFA4650B4E65718137DBE561046BD56AF8A938F899B9C6413AB634531944FDDAB1AAFD7C8EFC784197EE2BAE1847A42A37A4D1D7DE068BF58B86ACFA199D00BE6CA520222B482759F0FF7ECD5CAC3C98A8E8664588D7D623C720336D9E5498FF88FBEA5274C388C8F3E2AD7ED632B58E1E8E6E8DAAAE4377AA3B4B9FFF7632C50B5B114AEF13887C7A68FF8BB989E2EAA2FBCB45FF80782314EB61C72DCD7B4CEB6200EDB316F1D041FCE9103EB12713D6FDAF56A2583A23FC94B6E3B12C3D5AF57D44046A283D1EE247553C35AF3BAE88BD12BCF85C7E16D6BE232BF489588D38DABC84287FBE604F39C15672FFD4FBA05A7D7F0032B16BA2E7D1E389B692324370E5A0905370E1A6C62BAB8CC8999A12CDF912CF1D001307AEDF40D7AD18418B7E1E83CB9852E0E03116869CAA207990C2D7D005621D6B914789287591C678611E4CD7684AD13A8C26A553BAB4B88BCA264F3DEDACABFC79BC08828F6DF5862A2567B7EEB5A6D1A647FABA1124EF1DE031DCFE2E021512BFA8F1AA96ACDAC0408919F3F421C015014195D7C369DD9F35BB7B33CD09651FF58B39311A13EB7F62D231724F397AE6D2574C7AB465096DC8AC8410C81E1162340F13D09DFC63F36979B343B41A44B20A67358D070C72ED1C61F50E068E9A7BFB2EA0C0DF4C640E798DA15E5F03364D42F011A9A049084A6BECD1AFEC926CAC45CE0C852A32F77E23D33CBA56399A56C89E5DEBBEB226E68C66C7DAA2EBDA4B2B130BF1C1EB5B1DE0A4578F56412D54984509A6D7E323FB7CC71D5841613348216709DC1271D7101B71DCCA19889F1BB78D4D99E3D195B92BC44B1527364167360FE8344D9834CA4B21035A81B2DD1D2921FB790874547917103D523D41A684320AF6A9259FA469AE2DA41DCD7862B095F17D5FF13A97E4FD9C5E304B78BD2004F042FD34F8F80788ED4DA2CB33AB2B973CDA41A6C86C10E63A7625D00DFAF7EAF91BBED43BC5E9A2C595BE6C128991FD55BC9C982F7F4B6763B02A2B86A418C17143193AA0B0486B3F8A9D0AD58F105A464AAFA51D6A1BE3B835FFF7CC22CCF530536C9226811909EE18311D3A72DEDA5577F6C3400781CE21035D25FF6AC696B478DFEF0DDA98ECC49ACA1A9F3AEE28237C717F43DF1C4E70541D2CE3F9D9A8AF570F9644FFF739878956DD8392B527D8C9D5F5715B79B1C8D1FCCA3A559423B250DCD12DDD2B0D3DE399CDCC4CD37D978AA37777350152447BC08F6A6A9E7FF871C3AACB9487B27E748517D615B30374E5A6427A26B2ED43E5D361382632AD8FE77DF4ACE67AAC1BE42324B9A8882852BD92A6721AF78BF5AB335D53DD1B0B6C783A79FF61814A970D2055F33B0155559F6694E9EA86AC2C15BC085E6300696B526A7C80B2E5BFFA0B8416285ED84EF5CCA0D4A8194BF0073EEC7F4BB53E24D6B2D692E93FC6226A37E992642208664BF5E8F0FFA7C2B46A2C458315B61AE08A1208E16969589A9F07DF35958DE00A3ABDF13D9C6FC0C96F81F78836A9B88A72581265D80A9529DBAF80067AE09ED6D43F32522C2D8D1A3A749C4531DDFCB8E8B5BC8A7550FD7921424D034229918D8C61FCDAF850FC14D055125DA5D3321A6BCBCE755F0CE5E21DDFA0D009BB09F69065314493BDCC7F4F5154500786538A93295E63FAAC41ABA7A1901136EE65EDF2C7D68A9587147926E7047C34EBA622DC733A96B0FE98EA7FD93CD5E50CA5D077E70AEB4E466AFF675AE09964BA82F84522AFD7AD876E81466D0419FAD5200D91D4B0F5792C7D96B29A6F9808C71ECD9AAB30B1EFB313176944D42523DC9D23BF8DDF54D2C883ED2C552DFBE5F1B4625A5679CE9747DF46C035AAA26E8463FBC096B682A37D18C54B0B27397B86CF76804E7BD459DD7C400360E5ACADAEEAFF47C21503AED2F65D7A23313BEB80CAFE9F7C1AE33940F75CF567F069654FFCB57D59595C5EC2EDD27E6B373A4158AAFE38DF31A1D2397539133F0DBFBD0CCD82EB4AC9FA886D6464868DB20DE9C89719CBCD0C1DE202864460D5B5D5B92F66D18A8ABEF3E61F3D7AD4DF1BDE44D460119BF0D981DA327901FD7D10E3BA47CF3998AE42E0008361C6D8594767E6BF8B63FED04B91B277C79D618EB552F398900618356E61878D9207BBE4D7831FD5FE669616DD9ED602ED66BB8D968A8FF524233C8584DE2A4976A8075515B52E6E114F4BFE2656A348E11A0DC78D9568B5D5C1DCD5C013F94FA3EA4A4DBEF9FC4CC48884B16A4699ACD5B8541BA20892510F7FB4EF1240BD41496F54D03AEAFE0765A65764099BBF78BEE6C4E7EB0920D0908F6AC634389B420D8F14A1B199F64790B79B101F70591163C5EA8614EB09462A40D6018C2064857F36CB8E131983ADEE5A6EB9B66F67BD149F7E6340CFE33B98847C9E4D5062997442ECC29851A7FC52066EC6FBBE9EE5ADD488267375DA6FE840CA1993538EB74AE45C85F2866BC2BB3A42237C3F414E5EB0FF7AA54C7ED7B27470F4A170D59FC8A0D3ECE42BB1CC7BF4DDE23D20F5C75E200A216C84F4C617CE31520BA3AE25C2185246FAAD3DC40206D235F4A8FE7C576442912229D93B7E9C87B5C8A136E78C0CA0BB85E97301A45CFC67D1970399B53D8C6F11BB43299788BE791A5FD43FCBB625FBF1FA4B7F99E19E04B61546C797534AD6959B634E9445871C5931521AE4FDF6A838009E3F4C3F60D8539AD945E51661021D1CEFCDC9814B14B8132F6A02FA6E4E2AEB13ED9E706C0178206DE6BBE02A538C3BC704D54C311C9F15F0EF9A217152912D524869969D2C31FAB22691284FF22CD3D4BA5645A45AD5BC301B848630BC5399996C62875D8BAAF3DD74AA65F651833B36437267F86202028EC8FFA914B68B528F7DFA60B7247A1F1ACC9CB0DD05B6B23676757DE5860A98460030D334A740585FCAAE9A08AC78D86DF7CBD50480CF60BF5C782A5DF1B0997054F0B08646A21BE55CBEC48FAB854E6F1B617E597DCCA678EFE4A078E4466B92B3B1EA32128915839B65B9E010479C9C0F06F860926F7D2B29287201AB4BA36C36DEE4FD1D75777353CFB5A36486EF8AAE15184F75E1CBFCA4E78D117C30AAB18852A89620E0D86BF64B76E09FA1F898DCCC5CE361AB1ABF34E02A6EF9B09932A0D61525C835518F5E37B12E58E58A0A37E2D4D80B567FB342B7544348805029F50CC3D62B5E187E1125DE1022C9CF846CA0998F5DFE00AABD16395A15998770B2FA49BB7114796EDBB3BAFD5C615730CAF527D3C4679873C0EA24C967686EDF30629A90F9CF130DC12AB17E7A5AEDAED9942F0F93CE2D6AA1B046A5B931476931C1DCDD97CB46B85D67AF16A08B9768EB096838B5604CDFA9F9ED9BDAED533F3271EBAADC7A046C2104B3E4998FEC2A08C5FE8FB8EE739C3F6B0EC767E7E0C15FEADB010F1CE6F0D57B90D83037080F99E667B1D1006E70B025E4A8E900B3C7E1D74963C3B39250C6516B1B4FE36FCAD4DD251A5C6AE13384ED5DB2C10ACF27974045A83567E5777961E1AE529A072778AC1B5F98BA1FE46EBF91C787945355677B76CA67B1E898D61F1AAE731A9DCCC199F805E381AC31D303129786CE7C87B496F36D9FE1C7471A15F5DD2977B74A56100CD8AB794171A33AFFE1B66F61AFF7957F5A29AF6ACF9C49CCBF09FAB46C4ED2154D96A626293C20E19344E4CA10D361CFEB6F1578CCE811065684CA650465AA4DB69284705A5D6D2E78AF4F81DF81F041A88519F5D22F99A8F85257B277E60B3924947B3D391EE4147BDAC8FA341F3AC951340FF0CCD405FF33E3EF202A4545A533134E73DD7C9CB4BF2B35F3A57305891BFEE68E8C5311811EBF51DBB530D9BD886F9E0E18CE1DF2ED2CD7DB42E4B95321870286B5C9FD3B91967AE50FC26AEBC6DEB9CECD70BC21D0096071FD84D3D334E38404C78484717EF361E57B03999AC514E5F45FAF6B005C79CD7A5039C9433958F07C8F4C138350179C77C5F02915D0FC3A245F0613DE4DFC5D484B177F4D5DB4FF4255156778CAE7C56C7A17DF031413432B571C7A6274C7ED7E45861636A5F28882F8398B829B75490E00A3F49C05B8C3CE5F962CE76CDD8739CFF99788B469F0D8D428779A492B459B21E2107AF31CDD8B8EA7515AF2068E70D56DB115EF18699F3C5BDB9AC382690359779C168A6D71CF3BFC08EB5ECF719C54E498176DDFDFA48DF8AD5381E192EDC6365A848C1B5636C2FF056C2ECF8D9B264E0EE1E5D94E5021E07C2AA9586FD821C200EDC0B7843349B3AC83E2E76EE3AAD3C4AF20CF8E189F74D13EAEF06C1F86F5F99D8529CF364024287706493CAFAE45D9336AABF9864BC40C49D960FC2D29128F936CBAEBCAEF5AD1E2E52C7DAE9DB09A5D0A42FCE4430BB76456B7AEB70F7091FEA99C84E3CBD057539FEB816CFAE197BE9D6584B4536270BFE4154080A10A2F5E62EEBD61065F7755A22BC9BF005F688221E42C6BCD26130D832348F103BD39D49EE836F993A56637E38D0440797E949CBA59BF6BF0D7C3D5F82CF7840B5862C771A7C9EE6E28CD9E7A5095054DF827341AEA359193477B9618639A0B2922F843BC882990D33AA29C29D404BCA15E5985DA98AADF3B21E6A4EA5E8A2D025AB092DA5EC3795DF6053F1EE5C16E471F92DBA466655D495DA084212FB810986C827EC4EB623A0720AABC9EE8E04CA21BF824CC53C5FA9E495744560749886FE2A8F61F5006F7F8EBA255416302E08456B5E3376B33BFF4EC5E9019B4082EDD50B4DBED313F72EE69170469DE5F15B62AF6D89B0AB20E4D365C02E9BA611D1CCC32C8231C3B478C5F11E4A417B8E7DA38CAE00CCB2897DA507034E88AE9172A5F6DB5DB488BA7643CE82698200C4638FE02AC9290D0591EFDEE581BCF94213A3347B66A4B55E9E05A639E3CE7F448971CF092BB7EF68398EDA63C86D64B0355BA67969655B2C3F23D31FDBC2CB765B6D3CE210F10FE3A0338C3F8B64B190804A4E4AD6D3E56122CAFF7075C2EDC95FC9387D44ACA431FEFBBAAA3B38F1EAFE6AEE334F6578D59BA2F75EB3E927AB4EE5572A1CF7E0A6E784ECD6104F4C722F7111344C5ABE15EF385BC137A55D38CF63F8FA5638EDCC18DE2CB50E3EF13BA69285AA97D019FF341C4B1BAF64C850AEEADE0B0D16E5509AE93E095AB52DF7A341D8603233CCA11416AC687283365CB9A3BBD61DE4FB35073900BDF11CA6F4A65813469F78B337FD70988F8BC0332FFD007745A18B225A5E2ABCEC74C1028A890957A9CA6B24393D17FC4C708191C60D5B6AF1ABE5FEE64A7CA8B078EC1EBBA5A9F77A7BE62FCC408C46033B926B6E13A8E73E5D475AF9C06C360D9190AB458A13ACC7807090E7DAA7D4B52C7CB7F5F16711F902184E2FBA69718C060C67C6B34F03402A3EA47932C353D6E7BA403810DC0CFF1A3722BD7CB61C4EEC545351C90EA6C1836694D5E1ED55277EE75485D8F11F65A758C07A8C31CE4B93900B8258886559DA6CDE1C942A35DE813BDA8283AFF30040E5C54C508CE065FD3D71990717B1C671C9116A43240D344973E02DC13559BFEC430B149EB6074F4AF45D8DA57DE719175F983A31ABDFFD5EEBB5178576CEEB55FA7B88594AAFE5328408EBCB5D9C895488AE1D3BAA376BA021615D7CB9660C93FAC925619B2249DD9E08BB7F8C2C52054F107EAD823CF6776E792C5769EDD260B9E5F7800E0E395D6DBB24C3D463803D7A0D301597873D3E2E5791275D7E27ED6038E2420427960022C71C161F4658F2679705074EDC8BAA4186B9F3E4B9CDFEBD450660E7808E1A50FF19F65D757A76A6C2B4EF2B0C478BA79E2058D2845E02B17BD261CE638226867975EC7C1A1026A77E772706E79CA977F996A5F4B703DD2243862D7D769A98DFC3880D4631698340D77A31F6722738650D2C4EC125B3229659DC118F2F9E98C4542613718E71C0E6FA0C950101C2802C7D70E82399E98C5F62F7165E0E533997A5997EA617EB8659376E6E6707274A83266EB9710B233FE96D2006BD8CAEBCF77ABFCD9E5D6D226A6E5536866B69F937EDB3565F562B4963C86F0F5A21298BAF88F24F8887CB050D028C2818ECC388552623E27D05F049C0DDD5A52B1EE9168E95DEDB51574866846535FBCDC410393F7457428D7D1D31A62AABFBB5FADA1AB70CBE74CBF3913EB648FC7E1CA3AB1D4CC2D0C9150884858B5143FD5507CE28B629CC264D2DAF36954894FD69320533A3C50A56964244CB2C63BFD01299F52DA45A6DF88A59AD543A6EB73064C4985641BBFD0B084C77A2CA36A7A13A6DBF8A6241FD8B39496D7133B2FBCCE956CB30FFB5297CCF95664216C5FE7748618ACD5BD801029E5914BDBAFC8367791D69C31ACF9BFFAEF646547D544DBCAFC3A66F84241F99BB90BDF05C27060B72BF14860322C67F6D220B4F0CAE56AC8D59AEC614C72D8815C3CB0C2864BBE98CFD375051030A90ADAE737844BFA0C870C0C30BFC2CB65117D78421E2F8D9F84CE49BD34542169B06496250700C6DA624C5FD1840430A46AA7930350897A675AD742B7D0B80404C75E4792B7417E16825550633B5A2FF8D7BB80D468A6EE7F1E65BB86F257E93775E55FEEFEC37C8C9139159948B4B059C753930B167B78FAD6FB8EE340BA1E892A2D4506AE5314846B8B78F0AF59ED33CA700A43B083C784DC4B8E8B270A8D55AEA97C9CBA5836AEF4B51828ACDE4E176DFB988D1D543BB318A9B4CDA620E145B188025F9E5236AF934D51D0323C60D4AF89C2489EE368CEB1F8D6ACE5272A1C946B1E7289751ADC536257CBFFD140A1802E6AE29A1D36732BAFC333FC3078107CFA33CC6193551AFEDFA2805E5181B24D55CA8379B16E494042C1FBF4BCCD411E67630A0F6629827CC8D1D73BF8697B9325686357FF6CF6357D554ADAD15707745914A3DDC046C17C1EFE0A8D35E0B69E2A31B78B1057C7D04B985CEDE7194B3F1EEDC24707E0A83F7A9327F88FA905A5FF961BB9502C007D6765FAB715AE12C92095DA89DD8A47F904D970AC50D20D5C6CF874B97B14FB996EE090AE749B572167DBEBA6E0565FD106541151AF8F43659EF3265AC6A8A55A1173EB396A3BD6A6C862ABDEF3998B61583CE999E5F5FE687349C57A4F2FB24E2CA71CBB368EC70D944BCBC8807C018F07B967B7ABAEEE53777A2F095339CC2DCBC1FE6BB7CECDC2F7C78023745B662DA3F5B6254005985EFC1019B8E669BC105DE6B682FC7F7F69C7172B6003177079A098C50912D953F0A69554FC88015E5267712999C6897350D6402C695B8183D5BB4DBDE586D8CF5C7E4503434007365897651FB48B55F1839F058EC4557561753EC7731DABA93AF0437E184B5B4790874316786FB0222BBA896640354FEFE45FA40EB71C33E465D66A55F95AFF304977F091BB468FAF9F4CD34FD1E531FE8DDBDAD2C722BE88DF5DD629C12E18C07F4FCB9119AD5B4DD955DC81F241E16F76F20B9E49D30D3816B44BC655B31BFD53A6BCF1B5993E6279A626B241E037E85474A4473EADD155FCB35B8F9DF75504451C193F37DAC34C974E1AE41CF8EBD633E39426E0CDAE0D62E663CC16F18665BB85BDC4508580F0B311063F93512D4D209E9B8779915DCC14C1A3D2C1162C6DAC2233A702C22561A330FBE2D546865C695218A3FF9F5B4978AA073C9CDA91E478FB0E1D7894227B4D4D44DC31D2E63E955732DC170111A3AFF771A82E8BCCCBC6BBE6154E79C8EBD7F08E7DA97490861E6EFFAEDE7BBDF490FA3515BEF8CAE522F08DCECD9CEE36E55E2D3EBC82C346A2F248E79C525D888BDCF43D4BFB884A33AA1F04DA527DBF100616BA8B35F286C2EBD60F94E332DA80697070B4026BDE6CC5E64A9F85C0FE4EEE1D069ADEB21BACCA1C2C753CF952FDC6287AD5DC4604CAFDA5032AB3923002C91956B16C8D4C4AE7B81CEE8D2031DFA35CB19E744A8BAB7B56C55C379C6A50966D915FC78551EBF3723DED7F80616AABA7B11EF0172D33D87C352FAE89C4AD91FC3290FF3D9CE078EDE683C52C163268D7525C4C4934D6CC2D4E6D8FF0DE261AA06C048039D9AA4F294DCE923E228A6D5D5C5907BC6497448372BD09BC7AD1BEAC251DBC352051E8E07F7A184FDB1A098E2D45DF3277818A3CC24B07D3A442087463D6A2BC309BF576AD03B39B6CF60893446F4C5A2D2A64A5BA383FB5A148B4DCB368B0EE7E9B3640267F50E0C3AF0786B78559D761EA3F27D566AD4C836B5116C1A4DB2A3454767D3B5DBF75C80BD2583ECC18496A1240921BAB673C47893AF38A72A37C9E1BDC2556B95FF7C5DD4AFE0A37CEC60A607F348BE73A65E157355EE190A414ADE954232CCC77951589E21640AA84C5BD3DE83E11700CDDB08DB9D89C6B7CA90A166DDA1C916F31B855EBB0CB2D6C37948FD99302238D630A4BB0B10F19D6887E1B4602771453EC1DA4E7A4982B222F585418EEA3CCD74AFD66B7BA8E76D3E168B409F71E80F7A16678FC43BF7E75A08E6F3D6C4DCC13E66265DD3E5BB8A68A4307635BB29F0FDB43D191EAA1AF6A79E2FA0EE8318045347BA182C8E4B533F927698D001B541C3435F45EDC65AC43D443298DA09800C84BD8EF72053E76285F1F1FF9AD39D6BF511CED62A8C175533AF24CCE931212110582DF82ADE9EFC0EFFC64F0A6058165E92438F322055FC3AD682EC6EE1E8B57791E8510138854DB5238D2364451A2C88376BBCE2B9DA0104878C8EEF920E6A0A9215BDF10428A1A6557613A5866154BD552BE01E607EEDA05F50CABBE920F149A38477A50B6D3439AC21C9214D6E9E8145CBEF62B77E0F2D193DDF449ACEBED2E89493D0E1F4FA4A2057A766935868C69C1C397EAE3A926D90BB9BB0D5FAE780A847185C81A07CF724F24F2EA9C534615E1FB6C2FA6C4980E8EB561CD25C5C1657F19980B6981EFA2F447F644766567BC7ADE89ADC0241C8077FB705EEB2FE568C2BC03ADE978B416BAD43DB87878CA519427A921055294BDC6DDCCC800700D53404AD34FBE6766D4EB5FD1C4805412BDAD507F2B64CB457DB79CE6A16AE9CBED0165C1A33C5D6D5CA12202339E7DE75760A756266C52FB518FB048C9812EA794413F77C6AAE13E3E864584145B855A6905164343A64115757BAB251B139EDC55915788C3E7FE71243BAF7ABA56B76BBD03064C67D1C6615022BF234E125DA32F314441D9B1F9E07AB3571EA51B4A9DFB83AA01F4B721FD58E282FE6EBEAC244AA7EBDE775BCD3B398D18D0C8CCC2776D92062E62D77043983419D42566570EEC9BA24079BEC109CF13D2386A9BA053F542B12BC1AEBD1EC411CE9C1B38BC7EA2F74C2EBF7F791F0D2C89A8BD0B7BBEB2A67AF262902A6B43110A6DF73F72CEB1FE9F80AD2E6C8B1487BE1E342FE4E9C6034D2FA48FFC34EBF088BC2C3D3ECD5A93DAC6B2E25B729D05B50B79FCFAE7261C4521E85EBC6112CF7C0E30F0552C6867EBBFCD24E7F6A9A0B0E7CDD93BCD6EF419D23A7C1F5D3DE7242AFC462CF0EDD7A1A3521E4AA69BB9086CEE2AA5E995F37BEAEF696331EDFC7EDE4A69575F7037FBB19C7CB411DB81DF3A329904F0E19577C4FBC9B5615C4B158B2D6E610F37B93EB5277EAF3D71CC4B2BAB26AAED0FC9C4F6049B89FF7D3AEA0D74C5FC4F84F9DA183A5438C535FECBD54CBC1D0437A3046694391496A453FEF185BE999720B31C095DE5E0E5489715E5577042A111060B7B2161C4DE0117091E25DEADAFABA57D1E76AA6E665736E34160D2233D060CC4D0D46E438736B1918BE5842114D15A0C9F9F4FE63C8DCA69259BCB00D33AAD05BB1304665E7B2C579AF396B511964C7ED1D0374A82096923C1508221107B12B74C60EFB58C03460D9653A56DCD0FA80E72D678697D4BF8868176D3DAD434C5649CEFFC8115D1608F36AEBD71BBED2772A4A964A32EDE455D96D061DCD4E14EE2370F30A16F08078B440D817831190E9DDEB3539C179EC227B9001C1E33DFD8DA21F68ED1E08CBB0173E0D1967A5FFB8732DCE7E53C33D6D5CCD4F84C2F57A8F9C99A5C91B2C883175888E6EE5304104A24F9303B2EF4F5A3B4EEF6ADA2FEE49CC6EDB05FC69BA83FD96BA585E6C6A080FA2935AF888304C7A52FEF3EE3ED1E0741115F55B01F3D789501233314574F6FB1DA26F4C519F9EE58E80C2EDA280B1823A11A02D547EAF72D7F1DEDA47657DE9D3038BCF4783316C2925CBF14D8D5E8C7E86BD3584AACAD5172897D1B4517CF02EDB78B87DDE00B06EBF45FD903CADE69C09647F867FC80CD6E2F101A86A065761EAB89ACFAA2B88FFCBE3EE3D95F88D2922D38A156ACB86D175970B0251F282216BB59B0DA1994A27C606A111D6208B44AFC9C3776577EB85930B7107E36A9358C0A780CF9E7BEB561BD5005D8BE48652E4ECF83279BDA2AD527E34B53234803FFE7D344E421480610C1B5CBFE24E423C49785AA73F237DFE6BA815B08179702E58FE61A72F360028D80C5E1579FE0DC415C3C017F27039C69448FCE5B1B72A0EAEC50BAAD2FB62B53ECFB800C7BE9989268F32B36F2E455C231617C02B12EBB21EB1C68DDD48E0E6244D31A36B04FFD7504485688F95459A395719A27A0A7EE1DD64837916084D7519A6581EFCCCF159A5B31C56BF0747AEAF39E08025D45A1F6FD6433F96F2E6E91B69BD786FEE4D47C00FF3D9873BDA6A4047C61880BF55993E413413D69E9B99AF394AD8683B09DDDC0DA71D2267A723EA8BA8170FCDC91F4D2649F6FBF33E2FF67B6C1B3A6A7F29AA7BCFC28861360C8BAB1D8619DDD6251935418B52E3D4553B7063E30DFAC474C6029FC5FF26D4B77E267E2D3AE7D0D2044737808211EC14A1A6DD4A40752BCD3C8DFC33F9A281F4C63493B8CDC48281DFD1C7D6E7B94B575F4A0B686ECA951173BBE32F3CE7380B2E0B2511EF53E8FEBFAA664D70646E73F733A6D7FB029A0E32B1FB7491A2EF3D7BCD48D3D214BC82E720174DA68282400A75B834B41E6702F1431414984B7E2E1F9E29868DA1F85D6DEC3C53F467A9BAD4D6678B2B56EB591C86B091F1F294A940D5C1503A9E6359B5B210372C58351FB8A81A2D8FF7F84888B2D970CBD49256B8C1FDB93ABAA6CCE68FF054B4A084D12AC45A4D4AA9324399CDFB6E4F2B0AA363A8E9B47AE2097E5B36DF09A3360AB3D5FFB8552DDC3BA75FA59D3DDB6B0A66C3EFA528C58B1E069CE31F13C75EE76F0CC86190EFB0738793B057A822F3427D8A77F039CF2ED63E84FDC4C123ADC7EFE9C5EE9E9E57C1A9DF382C01CC401B04C72E874B3A3CCA874C6086A5E259845B2B1DCE6E7670FEC2D7D227A8BD4C56C226F5BD6ED3E07CC4346A79C7683ABD5CE354D1B240738D6DF1DF611B72C11E6D29BD984DBE02B2AC91C34034F8F899CC0D7085A097736B5C549DA27586E5DD0E34A4E52C686A49B3636C0B3396A68D5D65DA47DBE29A760BD17FE6B796264ED57EFD0CB9FAAC1A1F091255D1295B14CC3954FF755B6091E4C8E462C468D29A45CB210EC6B2F20C7CA7A75BC5D0F584A1BB81D018A0C0FCBA19DA09FEC794882070110612DF90B04E0AB182CDEF62343FDF9677D17BCF34A80C9999855912FEE8C3E494B1E9B2EADB1D5ECA058ABF3783F80ABE40D3925129601E51D55CA2B9241869723AE9EDC29240BCCFC201705632BF7AC3C43E2FCDE41DC0363D4DB5C0F678AB5A0623E82D6023BF63852307AAD680FC894EC985785CEE023BAF4812DFB3A495CEE2C7C4493D2EA60354879CC8BED8F298AE83E7EEA6221CE43B635DBA3D6C6D8D7D62403983C190582310858317205F2C689F31360FE82AF02B3943471E5DB72BAD4190933DDA53B2C888C2ED8CA1EE42AD8FC4C32E5DB0E6819A99889E7DDE5FD084724D3D71D53F09AF59EDE317C22420A8DA41AC4098A0662B931B1188F79C2CEF852F7B62DAF30FE01FD68438288A6C970C2FC9DE77BEBBA498E2FAD0A860C35248E64E93201B4649374716290966C0C8BF0939A5FA86246145714A35D75471BB1C4DCD49220EFCF2DB8D3320C8E76DBCAD82E03E799DDEFFC5F03FCD39ED48A66CBC22CAA746B6B601852EBA0F2F32BAD505FC2671BF8B9A27EFAECF3B1DA63ED88A4AF978A3B089B6F3942ACA28A166BA137EEFB359E0FA1E197529A45BA9A68E85675554CCBE9F319A21A63B68BEFD142EAAE624C8E0FB394FB30F830D1457F33831DA0355A338CE2B79333F5DF5158609EE66A49D41704D479880C542C7A5287989AD32FE8224B65AC10E799493C11E56BF0516E38CA89147AF841B62441C581C7C685BEF9E5D855ACA721312190F396E5DBE94A7FE8E78381722D37566D11405D2A310EC61EB78406B5DBE1F42C193398820C27C4F72D3CF014273850F0135B68C09038968FF136453A35D0DF9E3134F359DD33C4861C03CAE705798C260F7AC9BBFD053D2FF3D4DCFEE36C26E3A54C0FCCEBA159F0048F6F37519878A92A75E9981D63526D691B87ABBB4B4EC08AA359D9F66E6AE7A52CCE5954A6B56D08361974F533F36879B4B8D60D9B682CC1677B0D7001B18C7EF8A947EBABAF4035D276205C9CE6C7836AB268FF4005C14623DF6D555CDC29D03F737D851F81CC020C6B2532EF5D643AFFD446C8A2F162CCF12FC45D9115BFDA37FAA030EDB3EC75AB5F0528F6812A93FA78617393ACE595E3D2351DBC9EB495C1B3160C1023E25B6FA8DEDF99BDA1053F38CE35943F4BE274BC5D07EB6E6AB06BA13E677195829FF2B37DD6B59D35FF15E1259B842BEAA327F0BDBB1F732416674C2BBDD2632D7143B7186B209F1F46FF1EF123F91D9192F429DA1204C12C8FA86DFC31450D5CB3B0C43DEE5878D1F2D3519B7460A65A7066A5D70550E4B5BBD9BB457BCDCFEB458285EBEDB5F72665230B62B2D8AA4A658B4814EB4549A6D0AAE9423CF75A4253E045C24078DEDE774EB7A95DA4685F19F8F854A5E0C2D31924936B3DD8CE0E3B0A242C4E485F240EE956B429439274E0A7CBE109272F33B8BFF3FB8BE6D0DABB5A190AC909A31727A4C1A022FEF3955746DE9FF6EE43BA4DFD324BF9FB5E6A345D4CC35019DD961B34C233EEEAAE8B7B0F1676315A951E5DEB573E05C233D8F26F629DD2A5271AF294027ADB5ABA624C0DF20EF6136158B5151D72E9BDFDD289ABD70C904F317A28352EE0776C1AF7B8989D0014F33BE935DFD919EFDBE5552252A42D84AAB7FF95F315FFF75F764E0F27F27E74258D414C01B908CDE6D0A80E87B75C3F3DE7BDE1292DC94CEE362B9EC7EB9E64FF1AD962887998F4FD170921F1111CE49FC0446E6E189228219369730DCA874853CA58B44A9F06327F4B41883CD299B416E6F230975E8628A4961C6081AB20554D37109D1C65200A0407FB1EF2EE72FA25DA0CFED95BC5F33404BF17FCDE84D9DD40F520688CA0DE2D2548A850988627B9F281E38F46356B00DEFB9978C90F33949D4094D4530F0C9FFD9AAB25E6687523D0F43B77D8DEF65F94D847918E077291CAC7B5C0A55E74515280A24E9EAA7BE55FB67398100A001BD68F6E30A250C71AC9670729A73E75A6AB20849A361F008261D4FB3622D75B63CC7D8D93A50917093EECA446F1E894FE54CCAC51F3F3EA63D707F15465EB7E8C23569C65156FA5089247C23C1C7C6F1FB5AC3DA689E8C6CB684A64FDA63184CBC37077707C6E2EC775F542FB64EB2A69E596796B72ECB2BFE27068DB48F0AA0AFF335CA0F90F504C59A22A7B1DD4DCA2B6BF9375E3115CE6819A484358BC2605193688EB9BF87A3BF36616425CA72B94E45B8D8FC9487253EF2B034190B120D33FAD28A20E7C35307BFED50F7C3FB2A3AB5502BD940FAC4D745B3C412D08767507F73386CE7FF00ED735043445D20CE0C854FA7B75A60DB582F68D09888336943371A42EDF1AA0AC706FA16AE9BBA77A2955DBF9F1735856021F8FE108A650B8678A554BA0FD393F9EDEF4F89ECDCC8A764761FF259F1149D481ACAC7B01827BE66270840C52454E36DCB6E664155F564E4538FD80465B5FAE444D0B51C841BCA924EF78039C9F35A73A2402CFAC7ECBB1C30D2DE41DE303C7E2694A3C5C229CF25157A962113AFE15FDBA474A7F7F2A3BEFCE6240423A620C6DA3EF85BFE709AC3A7152182F1CC504A69998BFF864CB7159B2BC39FC295C2D3E3ACDBB82D91B86C919335007897C119EE5815E9611E1DA30CAA9392297E5EB2EC4CCFDE44892BF68EC65AC5089FE56CD06D7FBEC40D1A5DBC72D19F93D2A734A907D98F27D0633655AD92DB938D63151E620BA4919B6EFF89D3D1377D5424194031BB2496481100DFB62D14D51DFB05103F91DA5824E8782308E0AA52894061854872A7B9B63B1D72AA727C1AF6FB366950B6C0BE8D932363F6882FE2C955041520AB6A9B9DBAD817D2AFAC3A3C3478433A65FEC082188882E3A7260D3E62D5B9890120FB74E1EE8FAD5A83E20678517F9D1BD0B4FED7F07A43C1B1456114CC25C1FCBF4A6300B446CABA8E1F42479AAD99C351D4B76926CE7C31047FC83929F574B1CAC75B968164F8ABF65D4302A30A5BC5BD545B7C1CD540B9C530D40778835260A7187BBA1662EFB23AA11707FCE1D18E2A39DF87802B6F2CCC74048FB44C9B65FB6BB672846579F564D8E173245F61039172FD1E43CE187A0E9E673481E3102C6B242B472191FF9C32D5124CE840424229BF77F00EF94C224A4CABECEA72B5D9A31157940C15D30677974BDC6A58B51EAB9426E71449EF68F608675BE69350D2CE1777FA1DA393C835FA944ABB87CB1F060F87090EF365B21DB4117D0A2C0E1EB1D13A305217AE9241CE53A8E50319F6197C6EB6D0F964E02D2C85974610C4BB08376223A90BC7C123E5567974EDB2CA416FA92053BDAEE9805029BC56DDECAE14807B1170B148BF5CE31623FE6A981962ECB837BD5FB90863C7153D2A6C98810AE109B19B43F1C5C835318B2E54B6368DE19C8C55EBAC5FCF400DABF7536D6050BBC9763F410A8426AEF1AF3839162BFABC159D54027480AD06BA7EEA9D39ACE76332B0C8819B7E5AED882406F20C43A23D04795173BE9BFC816CE74E7B257DD73E21ECEB8CC6F8C62B90DA6214290D3AEE2F1D5964BBC760F9C522E214088003F87ED820EFD0D69AE28748C71E84CC3BB7E1BFBF810A1BFBEA535608A44F772ACF575320380ED27D5C8474711F23021F7BE762159B8F348FD70C5B71EBFCA3443ED1F4C2ECF24D5F09776E7D07F92D0760BB32DC4AD7D72EF951AC9440BEC6812FCA14DAD0E5FB8B126499A5C49133D1D734FF4FF5554FB238153908B16C05437BF208ED210F26A8092A54897B5500ADFA29E51FA6DAA827881514BBE28A0746005EC037B3AA5F844DFF16FA52664C10C5F6E6D6598CD105226D2D60B89088E29AD1DA46371C901007CB3AF95F857D6764625B5F53E6A6D5F7D4BC7173D89C5B7612790E07C5EC36C7D0C14F767B86D736F25D3BA575BD7D5BA679B76F1528B7BAD86A3FE57D854C5F8FC3D36E718A0A031004195F3CE3DB503C11698608FC1ECCDE0F2FA88DCA0002D9DA63F7382F97211EFE9E4CE472E1E14FE753FA513557684CD3DE7B968A2F574F45910129B674C8A6132215D9AA2AEC706495D0682D2EE56CEC3118D4C3E2CB9DA4F6E9D7BC6D4D785D44F3E11A10A57C6C95D94C818B710FB649BE3FB2D5754F62AACCA6428F947AAC712D3384BAAF6E3621D8FBFF82722E3576A21A604D8F11FCE626E5C26818255CA140CC887EEF50CD3EB378B25C0CCAF176086325128DA9F20AF8129F0C6B6C421397543A7DAA813F776798F964D5F78AEFCEECB13F6587BEBCF52128B04F8F3132EAAA2F8786F99E8749893B09821F1A0E9410E535687409B61A29B3FBDB4DE68B8B16110AB935C0C371C65C820DD7C4408530728FA79A10EBA688562BE0C5D86091FF867268E5E446A0BD7D9DCCFCC9708CFD1BFAC4551446E85D3C319F1BBD3893FAF55F3FC180A23A121418197A985E1E6B9A69BC6C2BFD53B83750355CAA9C2E4A3245E4DCB72ED15B7AD53BD4121AADB33BCA4948F27BBEA9B0CC9B5581A2D21B343173EB8ABBB51BB76A6E6F04C5DF065BAAE371DCE42084523689C9B9DEFAF07D86F70A1DB785337BC0763B26D6F4F6F2C0AB2593F7777B50E5F5D81031DE9EF67A0366F0826E12F87ABBF7D115065D640AAF2C49D6534B35C46E8C7D22C61B52E908EF5041AE3742BD9DBEACA79E8A2D05B6D2ECA2E3DBAEA122AB551A76D95872E574CA921D562B7DD665298EAE112DB663E2771AA459E33FE43CCD0F33054F55E3911674D7EBFDC2BC7CF7E5502665C40F4F1666230B8B19946FF88F733EE8D5EBB4F16B3CB8C679081F1439B761B7F134B1F862E1EDC7BE6ACA2D5FA94DA5964235ABD0993D21280F6175218C5BCEA4133C26DFFAFA5C870124679272A20A07AD423E4D81DE7BAE2F7963064728A156DBB58CD458C338BCE3924129F7908A06916454AD9B52F871407FB60315B23D601042D5E2D8446A260534668AE84AD8AC78F47FA0B3F9A861E2A9655D011F0449E1B4E045FCEF8A9CFCBE8EB541200BFF13806F131E5EC03EFEC816151BF4456225417BF544DE545B7EC2DFF43A682C3A2EAF4B6DFA5C4EFF2AC19CC6D28F8E067A872742A7DD675825E0B232EE518458238F79381BCD77EE60839126C5ADB83F9840DF68BAA47A7D4C5980EFB8EE1D94531F6B4ADC03E4A51E2940DBC8BF5D985884402D5B4801817CAA2A0F4452E8F13FAAD56B490D39334C8FEBAC8812CA29024AE2295788C460CA373F1CFFF32BA9C40BE67657B1F25319C6E2AF
+ss = E1FAA1034599A2694F72603451FBECD55FCABF135238A5D3D9BD97CE2F1BBB73
+
diff --git a/hqc-kem/src/code.rs b/hqc-kem/src/code.rs
new file mode 100644
index 00000000..22c2b268
--- /dev/null
+++ b/hqc-kem/src/code.rs
@@ -0,0 +1,18 @@
+/// Concatenated code: Reed-Solomon then Reed-Muller.
+use crate::params::HqcParameters;
+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);
+}
+
+/// 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);
+}
diff --git a/hqc-kem/src/error.rs b/hqc-kem/src/error.rs
new file mode 100644
index 00000000..4668b070
--- /dev/null
+++ b/hqc-kem/src/error.rs
@@ -0,0 +1,46 @@
+//! Error types for HQC-KEM operations.
+
+/// Errors that can occur during HQC operations.
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+    /// Invalid public key size.
+    #[error("invalid public key size: expected {expected}, got {got}")]
+    InvalidPublicKeySize {
+        /// Expected size.
+        expected: usize,
+        /// Actual size.
+        got: usize,
+    },
+    /// Invalid secret key size.
+    #[error("invalid secret key size: expected {expected}, got {got}")]
+    InvalidSecretKeySize {
+        /// Expected size.
+        expected: usize,
+        /// Actual size.
+        got: usize,
+    },
+    /// Invalid ciphertext size.
+    #[error("invalid ciphertext size: expected {expected}, got {got}")]
+    InvalidCiphertextSize {
+        /// Expected size.
+        expected: usize,
+        /// Actual size.
+        got: usize,
+    },
+    /// Invalid shared secret size.
+    #[error("invalid shared secret size: expected {expected}, got {got}")]
+    InvalidSharedSecretSize {
+        /// Expected size.
+        expected: usize,
+        /// Actual size.
+        got: usize,
+    },
+    /// Invalid message size for deterministic encapsulation.
+    #[error("invalid message size: expected {expected}, got {got}")]
+    InvalidMessageSize {
+        /// Expected size.
+        expected: usize,
+        /// Actual size.
+        got: usize,
+    },
+}
diff --git a/hqc-kem/src/fft.rs b/hqc-kem/src/fft.rs
new file mode 100644
index 00000000..69e8d3ea
--- /dev/null
+++ b/hqc-kem/src/fft.rs
@@ -0,0 +1,262 @@
+/// Additive FFT for GF(2^8) polynomial evaluation.
+///
+/// Used by Reed-Solomon decoding to find roots of the error locator polynomial.
+/// Based on Gao-Mateer with Bernstein-Chou-Schwabe improvements.
+use crate::gf256::{GF_LOG, gf_inverse, gf_mul, gf_square};
+use crate::params::PARAM_M;
+
+/// Compute FFT betas (basis elements).
+fn compute_fft_betas(betas: &mut [u16]) {
+    for (i, beta) in betas.iter_mut().enumerate().take(PARAM_M - 1) {
+        *beta = 1 << (PARAM_M - 1 - i);
+    }
+}
+
+/// Compute subset sums of a set.
+fn compute_subset_sums(subset_sums: &mut [u16], set: &[u16], set_size: usize) {
+    subset_sums[0] = 0;
+    for i in 0..set_size {
+        for j in 0..(1 << i) {
+            subset_sums[(1 << i) + j] = set[i] ^ subset_sums[j];
+        }
+    }
+}
+
+/// Radix conversion for small sizes (hardcoded cases).
+fn radix(f0: &mut [u16], f1: &mut [u16], f: &[u16], m_f: usize) {
+    match m_f {
+        1 => {
+            f0[0] = f[0];
+            f1[0] = f[1];
+        }
+        2 => {
+            f0[0] = f[0];
+            f0[1] = f[2] ^ f[3];
+            f1[0] = f[1] ^ f0[1];
+            f1[1] = f[3];
+        }
+        3 => {
+            f0[0] = f[0];
+            f0[2] = f[4] ^ f[6];
+            f0[3] = f[6] ^ f[7];
+            f1[1] = f[3] ^ f[5] ^ f[7];
+            f1[2] = f[5] ^ f[6];
+            f1[3] = f[7];
+            f0[1] = f[2] ^ f0[2] ^ f1[1];
+            f1[0] = f[1] ^ f0[1];
+        }
+        4 => {
+            f0[4] = f[8] ^ f[12];
+            f0[6] = f[12] ^ f[14];
+            f0[7] = f[14] ^ f[15];
+            f1[5] = f[11] ^ f[13];
+            f1[6] = f[13] ^ f[14];
+            f1[7] = f[15];
+            f0[5] = f[10] ^ f[12] ^ f1[5];
+            f1[4] = f[9] ^ f[13] ^ f0[5];
+
+            f0[0] = f[0];
+            f1[3] = f[7] ^ f[11] ^ f[15];
+            f0[3] = f[6] ^ f[10] ^ f[14] ^ f1[3];
+            f0[2] = f[4] ^ f0[4] ^ f0[3] ^ f1[3];
+            f1[1] = f[3] ^ f[5] ^ f[9] ^ f[13] ^ f1[3];
+            f1[2] = f[3] ^ f1[1] ^ f0[3];
+            f0[1] = f[2] ^ f0[2] ^ f1[1];
+            f1[0] = f[1] ^ f0[1];
+        }
+        _ => {
+            radix_big(f0, f1, f, m_f);
+        }
+    }
+}
+
+/// Radix conversion for larger sizes.
+fn radix_big(f0: &mut [u16], f1: &mut [u16], f: &[u16], m_f: usize) {
+    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];
+
+    q[..n].copy_from_slice(&f[3 * n..4 * n]);
+    q[n..2 * n].copy_from_slice(&f[3 * n..4 * n]);
+    r[..4 * n].copy_from_slice(&f[..4 * n]);
+
+    for i in 0..n {
+        q[i] ^= f[2 * n + i];
+        r[n + i] ^= q[i];
+    }
+
+    radix(&mut q0, &mut q1, &q, m_f - 1);
+    radix(&mut r0, &mut r1, &r, m_f - 1);
+
+    f0[..n].copy_from_slice(&r0[..n]);
+    f0[n..2 * n].copy_from_slice(&q0[..n]);
+    f1[..n].copy_from_slice(&r1[..n]);
+    f1[n..2 * n].copy_from_slice(&q1[..n]);
+}
+
+/// Recursive FFT evaluation.
+fn fft_rec(w: &mut [u16], f: &mut [u16], f_coeffs: usize, m: usize, m_f: usize, betas: &[u16]) {
+    if m_f == 1 {
+        let mut tmp = [0u16; PARAM_M];
+        for i in 0..m {
+            tmp[i] = gf_mul(betas[i], f[1]);
+        }
+        w[0] = f[0];
+        let mut x = 1usize;
+        for tmp_j in tmp.iter().take(m) {
+            for k in 0..x {
+                w[x + k] = w[k] ^ tmp_j;
+            }
+            x <<= 1;
+        }
+        return;
+    }
+
+    let half_size = 1 << (m_f - 1);
+    let mut f0 = vec![0u16; half_size];
+    let mut f1 = vec![0u16; half_size];
+
+    // Step 2: twist by beta_m
+    if betas[m - 1] != 1 {
+        let mut beta_pow = 1u16;
+        let x = 1usize << m_f;
+        for i in 1..x.min(f.len()) {
+            beta_pow = gf_mul(beta_pow, betas[m - 1]);
+            f[i] = gf_mul(beta_pow, f[i]);
+        }
+    }
+
+    // Step 3: radix
+    radix(&mut f0, &mut f1, f, m_f);
+
+    // Step 4: compute gammas and deltas
+    let mut gammas = vec![0u16; m];
+    let mut deltas = vec![0u16; 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);
+
+    // Step 5: recurse
+    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,
+    );
+
+    if f_coeffs <= 3 {
+        // f1 is constant
+        w[0] = u[0];
+        w[k] = u[0] ^ f1[0];
+        for i in 1..k {
+            w[i] = u[i] ^ gf_mul(gammas_sums[i], f1[0]);
+            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);
+
+        // Step 6: combine
+        w[k..k + k].copy_from_slice(&v[..k]);
+        w[0] = u[0];
+        w[k] ^= u[0];
+        for i in 1..k {
+            w[i] = u[i] ^ gf_mul(gammas_sums[i], v[i]);
+            w[k + i] ^= w[i];
+        }
+    }
+}
+
+/// Evaluate polynomial at all 2^M field elements using additive FFT.
+///
+/// `f` has `f_coeffs` coefficients. `w` receives 2^M evaluations.
+pub(crate) fn fft(w: &mut [u16; 256], f: &[u16], f_coeffs: usize, fft_param: usize) {
+    let mut betas = [0u16; PARAM_M - 1];
+    compute_fft_betas(&mut betas);
+
+    let mut betas_sums = [0u16; 1 << (PARAM_M - 1)];
+    compute_subset_sums(&mut betas_sums, &betas, PARAM_M - 1);
+
+    let fft_size = 1 << fft_param;
+    let mut f_padded = vec![0u16; 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 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];
+
+    fft_rec(
+        &mut u,
+        &mut f0,
+        f_coeffs.div_ceil(2),
+        PARAM_M - 1,
+        fft_param - 1,
+        &deltas,
+    );
+    fft_rec(
+        &mut v,
+        &mut f1,
+        f_coeffs / 2,
+        PARAM_M - 1,
+        fft_param - 1,
+        &deltas,
+    );
+
+    w[k..k + k].copy_from_slice(&v[..k]);
+    w[0] = u[0];
+    w[k] ^= u[0];
+    for i in 1..k {
+        w[i] = u[i] ^ gf_mul(betas_sums[i], v[i]);
+        w[k + i] ^= w[i];
+    }
+}
+
+/// Retrieve error polynomial from FFT evaluations.
+///
+/// `error[i] = 1` if `w[alpha^(-i)] == 0`, i.e., alpha^(-i) is a root of sigma.
+pub(crate) fn fft_retrieve_error_poly(error: &mut [u8; 256], w: &[u16; 256]) {
+    let mut gammas = [0u16; PARAM_M - 1];
+    compute_fft_betas(&mut gammas);
+
+    let mut gammas_sums = [0u16; 1 << (PARAM_M - 1)];
+    compute_subset_sums(&mut gammas_sums, &gammas, PARAM_M - 1);
+
+    let k = 1usize << (PARAM_M - 1);
+
+    // Check if 0 is root
+    error[0] ^= 1 ^ ((0u16.wrapping_sub(w[0])) >> 15) as u8;
+    // Check if 1 is root
+    error[0] ^= 1 ^ ((0u16.wrapping_sub(w[k])) >> 15) as u8;
+
+    for i in 1..k {
+        let idx1 = 255 - GF_LOG[gammas_sums[i] as usize] as usize;
+        error[idx1] ^= 1 ^ ((0u16.wrapping_sub(w[i])) >> 15) as u8;
+
+        let idx2 = 255 - GF_LOG[(gammas_sums[i] ^ 1) as usize] as usize;
+        error[idx2] ^= 1 ^ ((0u16.wrapping_sub(w[k + i])) >> 15) as u8;
+    }
+}
diff --git a/hqc-kem/src/gf256.rs b/hqc-kem/src/gf256.rs
new file mode 100644
index 00000000..df693b61
--- /dev/null
+++ b/hqc-kem/src/gf256.rs
@@ -0,0 +1,77 @@
+//! GF(2^8) arithmetic with irreducible polynomial x^8 + x^4 + x^3 + x^2 + 1 (0x11D).
+//!
+//! Generator (alpha) = 2.
+
+/// Powers of alpha: `gf_exp[i] = alpha^i`. Extended to 258 entries to avoid
+/// bounds checking in multiplication.
+pub(crate) static GF_EXP: [u16; 258] = [
+    1, 2, 4, 8, 16, 32, 64, 128, 29, 58, 116, 232, 205, 135, 19, 38, 76, 152, 45, 90, 180, 117,
+    234, 201, 143, 3, 6, 12, 24, 48, 96, 192, 157, 39, 78, 156, 37, 74, 148, 53, 106, 212, 181,
+    119, 238, 193, 159, 35, 70, 140, 5, 10, 20, 40, 80, 160, 93, 186, 105, 210, 185, 111, 222, 161,
+    95, 190, 97, 194, 153, 47, 94, 188, 101, 202, 137, 15, 30, 60, 120, 240, 253, 231, 211, 187,
+    107, 214, 177, 127, 254, 225, 223, 163, 91, 182, 113, 226, 217, 175, 67, 134, 17, 34, 68, 136,
+    13, 26, 52, 104, 208, 189, 103, 206, 129, 31, 62, 124, 248, 237, 199, 147, 59, 118, 236, 197,
+    151, 51, 102, 204, 133, 23, 46, 92, 184, 109, 218, 169, 79, 158, 33, 66, 132, 21, 42, 84, 168,
+    77, 154, 41, 82, 164, 85, 170, 73, 146, 57, 114, 228, 213, 183, 115, 230, 209, 191, 99, 198,
+    145, 63, 126, 252, 229, 215, 179, 123, 246, 241, 255, 227, 219, 171, 75, 150, 49, 98, 196, 149,
+    55, 110, 220, 165, 87, 174, 65, 130, 25, 50, 100, 200, 141, 7, 14, 28, 56, 112, 224, 221, 167,
+    83, 166, 81, 162, 89, 178, 121, 242, 249, 239, 195, 155, 43, 86, 172, 69, 138, 9, 18, 36, 72,
+    144, 61, 122, 244, 245, 247, 243, 251, 235, 203, 139, 11, 22, 44, 88, 176, 125, 250, 233, 207,
+    131, 27, 54, 108, 216, 173, 71, 142, 1, 2, 4,
+];
+
+/// Logarithm table: `gf_log[v] = i` where `alpha^i = v`. `gf_log[0] = 0` by convention.
+pub(crate) static GF_LOG: [u16; 256] = [
+    0, 0, 1, 25, 2, 50, 26, 198, 3, 223, 51, 238, 27, 104, 199, 75, 4, 100, 224, 14, 52, 141, 239,
+    129, 28, 193, 105, 248, 200, 8, 76, 113, 5, 138, 101, 47, 225, 36, 15, 33, 53, 147, 142, 218,
+    240, 18, 130, 69, 29, 181, 194, 125, 106, 39, 249, 185, 201, 154, 9, 120, 77, 228, 114, 166, 6,
+    191, 139, 98, 102, 221, 48, 253, 226, 152, 37, 179, 16, 145, 34, 136, 54, 208, 148, 206, 143,
+    150, 219, 189, 241, 210, 19, 92, 131, 56, 70, 64, 30, 66, 182, 163, 195, 72, 126, 110, 107, 58,
+    40, 84, 250, 133, 186, 61, 202, 94, 155, 159, 10, 21, 121, 43, 78, 212, 229, 172, 115, 243,
+    167, 87, 7, 112, 192, 247, 140, 128, 99, 13, 103, 74, 222, 237, 49, 197, 254, 24, 227, 165,
+    153, 119, 38, 184, 180, 124, 17, 68, 146, 217, 35, 32, 137, 46, 55, 63, 209, 91, 149, 188, 207,
+    205, 144, 135, 151, 178, 220, 252, 190, 97, 242, 86, 211, 171, 20, 42, 93, 158, 132, 60, 57,
+    83, 71, 109, 65, 162, 31, 45, 67, 216, 183, 123, 164, 118, 196, 23, 73, 236, 127, 12, 111, 246,
+    108, 161, 59, 82, 41, 157, 85, 170, 251, 96, 134, 177, 187, 204, 62, 90, 203, 89, 95, 176, 156,
+    169, 160, 81, 11, 245, 22, 235, 122, 117, 44, 215, 79, 174, 213, 233, 230, 231, 173, 232, 116,
+    214, 244, 234, 168, 80, 88, 175,
+];
+
+/// Constant-time GF(2^8) multiplication.
+///
+/// Uses bitwise carry-less multiplication with modular reduction by 0x11D.
+#[inline]
+pub(crate) fn gf_mul(a: u16, b: u16) -> u16 {
+    let mut a = a;
+    let mut r = a & (0u16.wrapping_sub(b & 1));
+    for i in 1..8 {
+        a = (a << 1) ^ ((0u16.wrapping_sub(a >> 7)) & 0x11D);
+        r ^= a & (0u16.wrapping_sub((b >> i) & 1));
+    }
+    r
+}
+
+/// GF(2^8) squaring: returns a^2.
+#[inline]
+pub(crate) fn gf_square(a: u16) -> u16 {
+    gf_mul(a, a)
+}
+
+/// GF(2^8) multiplicative inverse via the addition chain 1,2,3,4,7,11,15,30,60,120,127,254.
+///
+/// Returns 0 if a = 0.
+#[inline]
+pub(crate) fn gf_inverse(a: u16) -> u16 {
+    let inv = gf_square(a); // a^2
+    let tmp1 = gf_mul(inv, a); // a^3
+    let inv = gf_square(inv); // a^4
+    let tmp2 = gf_mul(inv, tmp1); // a^7
+    let tmp1 = gf_mul(inv, tmp2); // a^11
+    let mut inv = gf_mul(tmp1, inv); // a^15
+    inv = gf_square(inv); // a^30
+    inv = gf_square(inv); // a^60
+    inv = gf_square(inv); // a^120
+    inv = gf_mul(inv, tmp2); // a^127
+    inv = gf_square(inv); // a^254
+    inv
+}
diff --git a/hqc-kem/src/kem.rs b/hqc-kem/src/kem.rs
new file mode 100644
index 00000000..1ca61198
--- /dev/null
+++ b/hqc-kem/src/kem.rs
@@ -0,0 +1,188 @@
+/// HQC Key Encapsulation Mechanism with Fujisaki-Okamoto transform.
+///
+/// v5.0.0 KEM:
+/// - keygen: XOF(seed_kem) → seed_pke, sigma; pke_keygen(seed_pke)
+/// - encaps: m, salt from RNG; H(ek), G(H||m||salt) → kk, theta; encrypt(ek, m, theta)
+/// - decaps: decrypt → m'; re-encrypt; constant-time comparison; implicit rejection
+use crate::params::{HqcParameters, SALT_BYTES, SEED_BYTES, SS_BYTES};
+use crate::pke;
+use crate::poly;
+use crate::shake::{self, SeedExpander};
+use rand::CryptoRng;
+use zeroize::Zeroize;
+
+/// Deterministic KEM key generation from a 32-byte seed.
+///
+/// Returns (pk_bytes, sk_bytes) where:
+/// - pk = ek_pke (seed_pke_ek || s_bytes)
+/// - sk = ek_pke || dk_pke || sigma || seed_kem
+#[cfg(feature = "kgen")]
+pub(crate) fn keygen_deterministic(
+    seed_kem: &[u8; SEED_BYTES],
+    p: &HqcParameters,
+) -> (Vec<u8>, Vec<u8>) {
+    // XOF(seed_kem || 0x01) → raw read seed_pke + sigma
+    let mut ctx_kem = SeedExpander::new(seed_kem);
+    let mut seed_pke = [0u8; SEED_BYTES];
+    let mut sigma = vec![0u8; p.k];
+    ctx_kem.read_raw(&mut seed_pke);
+    ctx_kem.read_raw(&mut sigma);
+
+    // PKE keygen
+    let (ek_pke, dk_pke) = pke::pke_keygen(&seed_pke, p);
+
+    // Zeroize intermediate seed_pke — no longer needed after pke_keygen
+    seed_pke.zeroize();
+
+    // pk = ek_pke
+    let pk = ek_pke.clone();
+
+    // sk = ek_pke || dk_pke || sigma || seed_kem
+    let mut sk = Vec::with_capacity(p.sk_bytes);
+    sk.extend_from_slice(&ek_pke);
+    sk.extend_from_slice(&dk_pke);
+    sk.extend_from_slice(&sigma);
+    sk.extend_from_slice(seed_kem);
+
+    // Zeroize sigma intermediate — now copied into sk
+    sigma.zeroize();
+
+    (pk, sk)
+}
+
+/// KEM key generation.
+///
+/// Samples a random seed, then delegates to [`keygen_deterministic`].
+#[cfg(feature = "kgen")]
+pub(crate) fn keygen(p: &HqcParameters, rng: &mut impl CryptoRng) -> (Vec<u8>, Vec<u8>) {
+    let mut seed_kem = [0u8; SEED_BYTES];
+    rng.fill_bytes(&mut seed_kem);
+    let result = keygen_deterministic(&seed_kem, p);
+    seed_kem.zeroize();
+    result
+}
+
+/// Deterministic KEM encapsulation from a message and salt.
+///
+/// Returns (shared_secret, ciphertext).
+#[cfg(feature = "ecap")]
+pub(crate) fn encaps_deterministic(
+    ek_kem: &[u8],
+    m: &[u8],
+    salt: &[u8; SALT_BYTES],
+    p: &HqcParameters,
+) -> (Vec<u8>, Vec<u8>) {
+    // H(ek_kem || 0x01) → 32-byte hash of public key
+    let tmp_h = shake::hash_h(ek_kem);
+
+    // G(H || m || salt || 0x00) → 64 bytes: kk || theta
+    let mut g_input = Vec::with_capacity(32 + m.len() + SALT_BYTES);
+    g_input.extend_from_slice(&tmp_h);
+    g_input.extend_from_slice(m);
+    g_input.extend_from_slice(salt);
+    let mut tmp_g = shake::hash_g(&g_input);
+    g_input.zeroize();
+
+    let kk = tmp_g[..SS_BYTES].to_vec();
+    let theta = tmp_g[SS_BYTES..2 * SS_BYTES].to_vec();
+
+    // Encrypt
+    let c_pke = pke::pke_encrypt(ek_kem, m, &theta, p);
+
+    // Zeroize intermediates
+    tmp_g.zeroize();
+
+    // Ciphertext = c_pke || salt
+    let mut ct = Vec::with_capacity(p.ct_bytes);
+    ct.extend_from_slice(&c_pke);
+    ct.extend_from_slice(salt);
+
+    (kk, ct)
+}
+
+/// KEM encapsulation.
+///
+/// Samples random message and salt, then delegates to [`encaps_deterministic`].
+#[cfg(feature = "ecap")]
+pub(crate) fn encaps(
+    ek_kem: &[u8],
+    p: &HqcParameters,
+    rng: &mut (impl CryptoRng + ?Sized),
+) -> (Vec<u8>, Vec<u8>) {
+    let mut m = vec![0u8; p.k];
+    let mut salt = [0u8; SALT_BYTES];
+    rng.fill_bytes(&mut m);
+    rng.fill_bytes(&mut salt);
+    let result = encaps_deterministic(ek_kem, &m, &salt, p);
+    m.zeroize();
+    salt.zeroize();
+    result
+}
+
+/// KEM decapsulation with implicit rejection.
+///
+/// Returns shared secret (SS_BYTES).
+#[cfg(feature = "dcap")]
+pub(crate) fn decaps(dk_kem: &[u8], c_kem: &[u8], p: &HqcParameters) -> Vec<u8> {
+    // Parse secret key: ek_kem || dk_pke || sigma || seed_kem
+    let ek_kem = &dk_kem[..p.pk_bytes];
+    let dk_pke = &dk_kem[p.pk_bytes..p.pk_bytes + SEED_BYTES];
+    let sigma = &dk_kem[p.pk_bytes + SEED_BYTES..p.pk_bytes + SEED_BYTES + p.k];
+
+    // Parse ciphertext: c_pke || salt
+    let c_pke_len = p.n_bytes + p.n1n2_bytes;
+    let c_pke = &c_kem[..c_pke_len];
+    let salt = &c_kem[c_pke_len..c_pke_len + SALT_BYTES];
+
+    // Decrypt
+    let mut m_prime = pke::pke_decrypt(dk_pke, c_pke, p);
+
+    // Re-derive theta
+    let tmp_h = shake::hash_h(ek_kem);
+
+    let mut g_input = Vec::with_capacity(32 + p.k + SALT_BYTES);
+    g_input.extend_from_slice(&tmp_h);
+    g_input.extend_from_slice(&m_prime);
+    g_input.extend_from_slice(salt);
+    let mut tmp_g = shake::hash_g(&g_input);
+    g_input.zeroize();
+
+    let kk_prime = &tmp_g[..SS_BYTES];
+    let theta_prime = &tmp_g[SS_BYTES..2 * SS_BYTES];
+
+    // Re-encrypt
+    let c_pke_prime = pke::pke_encrypt(ek_kem, &m_prime, theta_prime, p);
+
+    // Zeroize decrypted message — no longer needed
+    m_prime.zeroize();
+
+    // Ciphertext for comparison: c_pke_prime || salt
+    let mut c_kem_prime = Vec::with_capacity(p.ct_bytes);
+    c_kem_prime.extend_from_slice(&c_pke_prime);
+    c_kem_prime.extend_from_slice(salt);
+
+    // J function for rejection key: SHA3-256(H || sigma || c_kem || 0x03)
+    let mut j_input = Vec::with_capacity(32 + p.k + p.ct_bytes);
+    j_input.extend_from_slice(&tmp_h);
+    j_input.extend_from_slice(sigma);
+    j_input.extend_from_slice(c_kem);
+    let mut k_rej = shake::hash_j(&j_input);
+    j_input.zeroize();
+
+    // Constant-time comparison of ciphertexts
+    let cmp_result = poly::vect_compare(&c_kem[..c_pke_len + SALT_BYTES], &c_kem_prime);
+
+    // Select between kk_prime and k_rej in constant time
+    // cmp_result is 0 if equal (use kk_prime), non-zero if different (use k_rej)
+    let mask = 0u8.wrapping_sub(cmp_result); // 0xFF if mismatch, 0x00 if match
+    let mut ss = vec![0u8; SS_BYTES];
+    for i in 0..SS_BYTES {
+        ss[i] = (kk_prime[i] & !mask) | (k_rej[i] & mask);
+    }
+
+    // Zeroize remaining sensitive intermediates
+    tmp_g.zeroize();
+    k_rej.zeroize();
+
+    ss
+}
diff --git a/hqc-kem/src/kem_impl.rs b/hqc-kem/src/kem_impl.rs
new file mode 100644
index 00000000..a24c9d71
--- /dev/null
+++ b/hqc-kem/src/kem_impl.rs
@@ -0,0 +1,209 @@
+//! Implementation of the [`kem`](kem_traits) crate traits for HQC-KEM.
+
+use crate::params::HqcParams;
+use crate::sizes;
+use crate::types::{DecapsulationKey, EncapsulationKey};
+use hybrid_array::Array;
+
+macro_rules! impl_hqc_kem {
+    ($params:ty, $pk_size:ty, $ct_size:ty) => {
+        // -- Kem on the parameter marker type --
+        impl kem_traits::Kem for $params {
+            type DecapsulationKey = DecapsulationKey<$params>;
+            type EncapsulationKey = EncapsulationKey<$params>;
+            type SharedKeySize = typenum::consts::U32;
+            type CiphertextSize = $ct_size;
+
+            fn generate_keypair_from_rng<R: rand::CryptoRng>(
+                rng: &mut R,
+            ) -> (
+                kem_traits::DecapsulationKey<Self>,
+                kem_traits::EncapsulationKey<Self>,
+            ) {
+                let (pk, sk) = crate::kem::keygen(<$params>::params(), rng);
+                let ek = EncapsulationKey::<$params>::from_vec(pk);
+                let dk = DecapsulationKey::<$params>::from_vec(sk);
+                (dk, ek)
+            }
+        }
+
+        // -- EncapsulationKey: KeySizeUser --
+        impl kem_traits::common::KeySizeUser for EncapsulationKey<$params> {
+            type KeySize = $pk_size;
+        }
+
+        // -- EncapsulationKey: TryKeyInit --
+        impl kem_traits::common::TryKeyInit for EncapsulationKey<$params> {
+            fn new(key: &kem_traits::Key<Self>) -> Result<Self, kem_traits::InvalidKey> {
+                let bytes = key.as_slice();
+                if bytes.len() != <$params>::PK_BYTES {
+                    return Err(kem_traits::InvalidKey);
+                }
+                Ok(EncapsulationKey::<$params>::from_vec(bytes.to_vec()))
+            }
+        }
+
+        // -- EncapsulationKey: KeyExport --
+        impl kem_traits::common::KeyExport for EncapsulationKey<$params> {
+            fn to_bytes(&self) -> kem_traits::Key<Self> {
+                let mut arr = Array::<u8, $pk_size>::default();
+                arr.as_mut_slice().copy_from_slice(self.as_ref());
+                arr
+            }
+        }
+
+        // -- EncapsulationKey: Encapsulate --
+        impl kem_traits::Encapsulate for EncapsulationKey<$params> {
+            type Kem = $params;
+
+            fn encapsulate_with_rng<R>(
+                &self,
+                rng: &mut R,
+            ) -> (
+                kem_traits::Ciphertext<Self::Kem>,
+                kem_traits::SharedKey<Self::Kem>,
+            )
+            where
+                R: rand::CryptoRng + ?Sized,
+            {
+                let (ss_vec, ct_vec) = crate::kem::encaps(self.as_ref(), <$params>::params(), rng);
+
+                let mut ct = Array::<u8, $ct_size>::default();
+                ct.as_mut_slice().copy_from_slice(&ct_vec);
+
+                let mut ss = Array::<u8, typenum::consts::U32>::default();
+                ss.as_mut_slice().copy_from_slice(&ss_vec);
+
+                (ct, ss)
+            }
+        }
+
+        // -- DecapsulationKey: KeySizeUser (seed = 32 bytes) --
+        impl kem_traits::common::KeySizeUser for DecapsulationKey<$params> {
+            type KeySize = typenum::consts::U32;
+        }
+
+        // -- DecapsulationKey: KeyInit (from 32-byte seed) --
+        impl kem_traits::common::KeyInit for DecapsulationKey<$params> {
+            fn new(seed: &kem_traits::Key<Self>) -> Self {
+                let seed_arr: [u8; 32] = seed
+                    .as_slice()
+                    .try_into()
+                    .expect("seed is exactly 32 bytes");
+                let (pk, sk) = crate::kem::keygen_deterministic(&seed_arr, <$params>::params());
+                let _ = pk; // pk is embedded in sk
+                DecapsulationKey::<$params>::from_vec(sk)
+            }
+        }
+
+        // -- DecapsulationKey: KeyExport (returns 32-byte seed) --
+        impl kem_traits::common::KeyExport for DecapsulationKey<$params> {
+            fn to_bytes(&self) -> kem_traits::Key<Self> {
+                let sk = self.as_ref();
+                let seed_start = sk.len() - crate::params::SEED_BYTES;
+                let mut arr = Array::<u8, typenum::consts::U32>::default();
+                arr.as_mut_slice().copy_from_slice(&sk[seed_start..]);
+                arr
+            }
+        }
+
+        // -- DecapsulationKey: Generate --
+        impl kem_traits::common::Generate for DecapsulationKey<$params> {
+            fn try_generate_from_rng<R>(rng: &mut R) -> Result<Self, <R as rand::TryRng>::Error>
+            where
+                R: rand::TryCryptoRng + ?Sized,
+            {
+                let mut seed = [0u8; 32];
+                rng.try_fill_bytes(&mut seed)?;
+                let seed_arr = Array::<u8, typenum::consts::U32>::from(seed);
+                Ok(<Self as kem_traits::common::KeyInit>::new(&seed_arr))
+            }
+        }
+
+        // -- DecapsulationKey: Decapsulator --
+        impl kem_traits::Decapsulator for DecapsulationKey<$params> {
+            type Kem = $params;
+
+            fn encapsulation_key(&self) -> &kem_traits::EncapsulationKey<Self::Kem> {
+                self.encapsulation_key()
+            }
+        }
+
+        // -- DecapsulationKey: Decapsulate --
+        impl kem_traits::Decapsulate for DecapsulationKey<$params> {
+            fn decapsulate(
+                &self,
+                ct: &kem_traits::Ciphertext<Self::Kem>,
+            ) -> kem_traits::SharedKey<Self::Kem> {
+                let ss_vec = crate::kem::decaps(self.as_ref(), ct.as_slice(), <$params>::params());
+                let mut ss = Array::<u8, typenum::consts::U32>::default();
+                ss.as_mut_slice().copy_from_slice(&ss_vec);
+                ss
+            }
+        }
+    };
+}
+
+impl_hqc_kem!(crate::params::Hqc128Params, sizes::U2241, sizes::U4433);
+impl_hqc_kem!(crate::params::Hqc192Params, sizes::U4514, sizes::U8978);
+impl_hqc_kem!(crate::params::Hqc256Params, sizes::U7237, sizes::U14421);
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use kem_traits::common::{Generate, KeyExport, KeyInit};
+    use kem_traits::{Decapsulate, Encapsulate, Kem};
+
+    macro_rules! kem_roundtrip_test {
+        ($name:ident, $params:ty) => {
+            #[test]
+            fn $name() {
+                // Generate via kem trait
+                let mut rng = rand::rng();
+                let (dk, ek) = <$params>::generate_keypair_from_rng(&mut rng);
+
+                // Encapsulate
+                let (ct, ss1) = ek.encapsulate_with_rng(&mut rng);
+
+                // Decapsulate (use UFCS to call trait method, not inherent)
+                let ss2 = <_ as Decapsulate>::decapsulate(&dk, &ct);
+                assert_eq!(ss1, ss2);
+
+                // Verify Decapsulator returns the right EK
+                let ek_ref = kem_traits::Decapsulator::encapsulation_key(&dk);
+                assert_eq!(&ek, ek_ref);
+            }
+        };
+    }
+
+    kem_roundtrip_test!(kem_roundtrip_128, crate::params::Hqc128Params);
+    kem_roundtrip_test!(kem_roundtrip_192, crate::params::Hqc192Params);
+    kem_roundtrip_test!(kem_roundtrip_256, crate::params::Hqc256Params);
+
+    macro_rules! from_seed_test {
+        ($name:ident, $params:ty) => {
+            #[test]
+            fn $name() {
+                // Generate DK, export seed, re-import, verify deterministic
+                let dk = DecapsulationKey::<$params>::generate_from_rng(&mut rand::rng());
+                let seed = dk.to_bytes();
+                let dk2 = DecapsulationKey::<$params>::new(&seed);
+
+                // Both should produce the same EK
+                let ek1 = kem_traits::Decapsulator::encapsulation_key(&dk);
+                let ek2 = kem_traits::Decapsulator::encapsulation_key(&dk2);
+                assert_eq!(ek1, ek2);
+
+                // Both should produce the same shared secret
+                let mut rng = rand::rng();
+                let (ct, ss1) = ek1.encapsulate_with_rng(&mut rng);
+                let ss2 = <_ as Decapsulate>::decapsulate(&dk2, &ct);
+                assert_eq!(ss1, ss2);
+            }
+        };
+    }
+
+    from_seed_test!(from_seed_128, crate::params::Hqc128Params);
+    from_seed_test!(from_seed_192, crate::params::Hqc192Params);
+    from_seed_test!(from_seed_256, crate::params::Hqc256Params);
+}
diff --git a/hqc-kem/src/lib.rs b/hqc-kem/src/lib.rs
new file mode 100644
index 00000000..d0d2a27d
--- /dev/null
+++ b/hqc-kem/src/lib.rs
@@ -0,0 +1,189 @@
+//! Pure Rust implementation of HQC-KEM (NIST FIPS 207).
+//!
+//! HQC is a code-based Key Encapsulation Mechanism using quasi-cyclic codes
+//! over the ring Z_2\[X\]/(X^n-1). Uses concatenated Reed-Solomon + Reed-Muller
+//! error correction with Fujisaki-Okamoto transform for IND-CCA2 security.
+//!
+//! # Usage
+//!
+//! ```rust
+//! # #[cfg(all(feature = "kgen", feature = "ecap", feature = "dcap"))]
+//! # {
+//! use hqc_kem::{Hqc128, HqcKem};
+//!
+//! let mut rng = rand::rng();
+//! let (ek, dk) = Hqc128::generate_key(&mut rng);
+//! let (ct, ss1) = ek.encapsulate(&mut rng);
+//! let ss2 = dk.decapsulate(&ct);
+//! assert_eq!(ss1, ss2);
+//! # }
+//! ```
+//!
+//! # Security Levels
+//!
+//! - [`Hqc128`] / [`hqc128`]: NIST Level 1 (128-bit security)
+//! - [`Hqc192`] / [`hqc192`]: NIST Level 3 (192-bit security)
+//! - [`Hqc256`] / [`hqc256`]: NIST Level 5 (256-bit security)
+//!
+//! # Features
+//!
+//! - `kgen`: Key generation (default)
+//! - `ecap`: Encapsulation (default)
+//! - `dcap`: Decapsulation (default)
+//! - `kem`: RustCrypto [`kem`](https://crates.io/crates/kem) 0.3 trait implementations
+//! - `pkcs8`: PKCS#8 key encoding/decoding
+//! - `pem`: PEM encoding (enables `pkcs8/pem`)
+//! - `alloc`: Enables PKCS#8 encoding (requires `alloc`)
+//! - `serde`: Serde serialization support
+
+#[cfg(any(feature = "kgen", feature = "ecap", feature = "dcap"))]
+mod code;
+mod error;
+#[cfg(any(feature = "kgen", feature = "ecap", feature = "dcap"))]
+mod fft;
+#[cfg(any(feature = "kgen", feature = "ecap", feature = "dcap"))]
+mod gf256;
+#[cfg(any(feature = "kgen", feature = "ecap", feature = "dcap"))]
+mod kem;
+#[cfg(feature = "kem")]
+mod kem_impl;
+mod params;
+#[cfg(feature = "pkcs8")]
+mod pkcs8_impl;
+#[cfg(any(feature = "kgen", feature = "ecap", feature = "dcap"))]
+mod pke;
+#[cfg(any(feature = "kgen", feature = "ecap", feature = "dcap"))]
+mod poly;
+#[cfg(any(feature = "kgen", feature = "ecap", feature = "dcap"))]
+mod reed_muller;
+#[cfg(any(feature = "kgen", feature = "ecap", feature = "dcap"))]
+mod reed_solomon;
+#[cfg(any(feature = "kgen", feature = "ecap", feature = "dcap"))]
+mod sampling;
+#[cfg(any(feature = "kgen", feature = "ecap", feature = "dcap"))]
+mod shake;
+#[cfg(feature = "kem")]
+mod sizes;
+mod types;
+
+pub use error::Error;
+pub use params::{Hqc128Params, Hqc192Params, Hqc256Params, HqcParams};
+pub use types::{Ciphertext, DecapsulationKey, EncapsulationKey, HqcKem, SharedSecret};
+
+/// HQC-128 KEM (NIST Level 1).
+pub type Hqc128 = HqcKem<Hqc128Params>;
+/// HQC-192 KEM (NIST Level 3).
+pub type Hqc192 = HqcKem<Hqc192Params>;
+/// HQC-256 KEM (NIST Level 5).
+pub type Hqc256 = HqcKem<Hqc256Params>;
+
+/// HQC-128: NIST Level 1 security (128-bit).
+pub mod hqc128 {
+    /// Public key size in bytes.
+    pub const PUBLIC_KEY_SIZE: usize = 2241;
+    /// Secret key size in bytes.
+    pub const SECRET_KEY_SIZE: usize = 2321;
+    /// Ciphertext size in bytes.
+    pub const CIPHERTEXT_SIZE: usize = 4433;
+    /// Shared secret size in bytes.
+    pub const SHARED_SECRET_SIZE: usize = crate::params::SS_BYTES;
+    /// Message size in bytes (for deterministic encapsulation).
+    pub const MESSAGE_SIZE: usize = 16;
+    /// Salt size in bytes (for deterministic encapsulation).
+    pub const SALT_SIZE: usize = crate::params::SALT_BYTES;
+
+    /// HQC-128 encapsulation key.
+    pub type EncapsulationKey = crate::EncapsulationKey<crate::Hqc128Params>;
+    /// HQC-128 decapsulation key.
+    pub type DecapsulationKey = crate::DecapsulationKey<crate::Hqc128Params>;
+    /// HQC-128 ciphertext.
+    pub type Ciphertext = crate::Ciphertext<crate::Hqc128Params>;
+    /// HQC-128 shared secret.
+    pub type SharedSecret = crate::SharedSecret<crate::Hqc128Params>;
+
+    /// Generate an HQC-128 key pair.
+    #[cfg(feature = "kgen")]
+    pub fn generate_key(rng: &mut impl rand::CryptoRng) -> (EncapsulationKey, DecapsulationKey) {
+        crate::Hqc128::generate_key(rng)
+    }
+
+    /// Generate an HQC-128 key pair deterministically from a 32-byte seed.
+    #[cfg(feature = "kgen")]
+    pub fn generate_key_deterministic(seed: &[u8; 32]) -> (EncapsulationKey, DecapsulationKey) {
+        crate::Hqc128::generate_key_deterministic(seed)
+    }
+}
+
+/// HQC-192: NIST Level 3 security (192-bit).
+pub mod hqc192 {
+    /// Public key size in bytes.
+    pub const PUBLIC_KEY_SIZE: usize = 4514;
+    /// Secret key size in bytes.
+    pub const SECRET_KEY_SIZE: usize = 4602;
+    /// Ciphertext size in bytes.
+    pub const CIPHERTEXT_SIZE: usize = 8978;
+    /// Shared secret size in bytes.
+    pub const SHARED_SECRET_SIZE: usize = crate::params::SS_BYTES;
+    /// Message size in bytes (for deterministic encapsulation).
+    pub const MESSAGE_SIZE: usize = 24;
+    /// Salt size in bytes (for deterministic encapsulation).
+    pub const SALT_SIZE: usize = crate::params::SALT_BYTES;
+
+    /// HQC-192 encapsulation key.
+    pub type EncapsulationKey = crate::EncapsulationKey<crate::Hqc192Params>;
+    /// HQC-192 decapsulation key.
+    pub type DecapsulationKey = crate::DecapsulationKey<crate::Hqc192Params>;
+    /// HQC-192 ciphertext.
+    pub type Ciphertext = crate::Ciphertext<crate::Hqc192Params>;
+    /// HQC-192 shared secret.
+    pub type SharedSecret = crate::SharedSecret<crate::Hqc192Params>;
+
+    /// Generate an HQC-192 key pair.
+    #[cfg(feature = "kgen")]
+    pub fn generate_key(rng: &mut impl rand::CryptoRng) -> (EncapsulationKey, DecapsulationKey) {
+        crate::Hqc192::generate_key(rng)
+    }
+
+    /// Generate an HQC-192 key pair deterministically from a 32-byte seed.
+    #[cfg(feature = "kgen")]
+    pub fn generate_key_deterministic(seed: &[u8; 32]) -> (EncapsulationKey, DecapsulationKey) {
+        crate::Hqc192::generate_key_deterministic(seed)
+    }
+}
+
+/// HQC-256: NIST Level 5 security (256-bit).
+pub mod hqc256 {
+    /// Public key size in bytes.
+    pub const PUBLIC_KEY_SIZE: usize = 7237;
+    /// Secret key size in bytes.
+    pub const SECRET_KEY_SIZE: usize = 7333;
+    /// Ciphertext size in bytes.
+    pub const CIPHERTEXT_SIZE: usize = 14421;
+    /// Shared secret size in bytes.
+    pub const SHARED_SECRET_SIZE: usize = crate::params::SS_BYTES;
+    /// Message size in bytes (for deterministic encapsulation).
+    pub const MESSAGE_SIZE: usize = 32;
+    /// Salt size in bytes (for deterministic encapsulation).
+    pub const SALT_SIZE: usize = crate::params::SALT_BYTES;
+
+    /// HQC-256 encapsulation key.
+    pub type EncapsulationKey = crate::EncapsulationKey<crate::Hqc256Params>;
+    /// HQC-256 decapsulation key.
+    pub type DecapsulationKey = crate::DecapsulationKey<crate::Hqc256Params>;
+    /// HQC-256 ciphertext.
+    pub type Ciphertext = crate::Ciphertext<crate::Hqc256Params>;
+    /// HQC-256 shared secret.
+    pub type SharedSecret = crate::SharedSecret<crate::Hqc256Params>;
+
+    /// Generate an HQC-256 key pair.
+    #[cfg(feature = "kgen")]
+    pub fn generate_key(rng: &mut impl rand::CryptoRng) -> (EncapsulationKey, DecapsulationKey) {
+        crate::Hqc256::generate_key(rng)
+    }
+
+    /// Generate an HQC-256 key pair deterministically from a 32-byte seed.
+    #[cfg(feature = "kgen")]
+    pub fn generate_key_deterministic(seed: &[u8; 32]) -> (EncapsulationKey, DecapsulationKey) {
+        crate::Hqc256::generate_key_deterministic(seed)
+    }
+}
diff --git a/hqc-kem/src/params.rs b/hqc-kem/src/params.rs
new file mode 100644
index 00000000..3b2fa33e
--- /dev/null
+++ b/hqc-kem/src/params.rs
@@ -0,0 +1,235 @@
+//! HQC parameter definitions for all security levels.
+
+/// Seed size in bytes.
+#[cfg(any(
+    feature = "kgen",
+    feature = "ecap",
+    feature = "dcap",
+    feature = "pkcs8",
+    feature = "kem"
+))]
+pub(crate) const SEED_BYTES: usize = 32;
+/// Salt size in bytes.
+pub(crate) const SALT_BYTES: usize = 16;
+/// Shared secret size in bytes.
+pub(crate) const SS_BYTES: usize = 32;
+/// GF(2^M) parameter M.
+#[cfg(any(feature = "kgen", feature = "ecap", feature = "dcap"))]
+pub(crate) const PARAM_M: usize = 8;
+
+/// Internal runtime parameter set.
+#[doc(hidden)]
+#[derive(Debug, Clone, Copy)]
+pub struct HqcParameters {
+    /// Ring dimension.
+    pub n: usize,
+    /// RS code length.
+    pub n1: usize,
+    /// RM code length.
+    pub n2: usize,
+    /// Concatenated code length n1*n2.
+    pub n1n2: usize,
+    /// Message length in bytes.
+    pub k: usize,
+    /// RS error-correction capability.
+    pub delta: usize,
+    /// Secret key Hamming weight.
+    pub w: usize,
+    /// Error vector Hamming weight.
+    pub w_e: usize,
+    /// Random vector Hamming weight.
+    pub w_r: usize,
+    /// FFT parameter: 2^fft >= delta+1.
+    pub fft: usize,
+    /// Byte size of n-bit vector: ceil(n/8).
+    pub n_bytes: usize,
+    /// Byte size of n1n2-bit vector: ceil(n1n2/8).
+    pub n1n2_bytes: usize,
+    /// u64 array size for n-bit vector: ceil(n/64).
+    pub vec_n_size_64: usize,
+    /// u64 array size for n1n2-bit vector: ceil(n1n2/64).
+    pub vec_n1n2_size_64: usize,
+    /// Public key size in bytes: SEED_BYTES + n_bytes.
+    pub pk_bytes: usize,
+    /// Secret key size in bytes: pk_bytes + SEED_BYTES + k + SEED_BYTES.
+    pub sk_bytes: usize,
+    /// Ciphertext size in bytes: n_bytes + n1n2_bytes + SALT_BYTES.
+    pub ct_bytes: usize,
+    /// Reduction mask for top u64 word.
+    pub red_mask: u64,
+    /// Barrett reciprocal for constant-time modular reduction: floor(2^32 / n).
+    pub barrett_recip: u32,
+    /// RS generator polynomial coefficients (ascending degree, monic).
+    pub rs_poly: &'static [u8],
+}
+
+/// RS generator polynomial for HQC-128 (degree 30, delta=15).
+static RS_POLY_128: [u8; 31] = [
+    89, 69, 153, 116, 176, 117, 111, 75, 73, 233, 242, 233, 65, 210, 21, 139, 103, 173, 67, 118,
+    105, 210, 174, 110, 74, 69, 228, 82, 255, 181, 1,
+];
+
+/// RS generator polynomial for HQC-192 (degree 32, delta=16).
+static RS_POLY_192: [u8; 33] = [
+    45, 216, 239, 24, 253, 104, 27, 40, 107, 50, 163, 210, 227, 134, 224, 158, 119, 13, 158, 1,
+    238, 164, 82, 43, 15, 232, 246, 142, 50, 189, 29, 232, 1,
+];
+
+/// RS generator polynomial for HQC-256 (degree 58, delta=29).
+static RS_POLY_256: [u8; 59] = [
+    49, 167, 49, 39, 200, 121, 124, 91, 240, 63, 148, 71, 150, 123, 87, 101, 32, 215, 159, 71, 201,
+    115, 97, 210, 186, 183, 141, 217, 123, 12, 31, 243, 180, 219, 152, 239, 99, 141, 4, 246, 191,
+    144, 8, 232, 47, 27, 141, 178, 130, 64, 124, 47, 39, 188, 216, 48, 199, 187, 1,
+];
+
+/// HQC-128 parameters.
+pub(crate) const HQC_128: HqcParameters = HqcParameters {
+    n: 17669,
+    n1: 46,
+    n2: 384,
+    n1n2: 17664,
+    k: 16,
+    delta: 15,
+    w: 66,
+    w_e: 75,
+    w_r: 75,
+    fft: 4,
+    n_bytes: 2209,
+    n1n2_bytes: 2208,
+    vec_n_size_64: 277,
+    vec_n1n2_size_64: 276,
+    pk_bytes: 2241,
+    sk_bytes: 2321,
+    ct_bytes: 4433,
+    red_mask: 0x1f,
+    barrett_recip: 243079, // floor(2^32 / 17669)
+    rs_poly: &RS_POLY_128,
+};
+
+/// HQC-192 parameters.
+pub(crate) const HQC_192: HqcParameters = HqcParameters {
+    n: 35851,
+    n1: 56,
+    n2: 640,
+    n1n2: 35840,
+    k: 24,
+    delta: 16,
+    w: 100,
+    w_e: 114,
+    w_r: 114,
+    fft: 5,
+    n_bytes: 4482,
+    n1n2_bytes: 4480,
+    vec_n_size_64: 561,
+    vec_n1n2_size_64: 560,
+    pk_bytes: 4514,
+    sk_bytes: 4602,
+    ct_bytes: 8978,
+    red_mask: 0x7ff,
+    barrett_recip: 119800, // floor(2^32 / 35851)
+    rs_poly: &RS_POLY_192,
+};
+
+/// HQC-256 parameters.
+pub(crate) const HQC_256: HqcParameters = HqcParameters {
+    n: 57637,
+    n1: 90,
+    n2: 640,
+    n1n2: 57600,
+    k: 32,
+    delta: 29,
+    w: 131,
+    w_e: 149,
+    w_r: 149,
+    fft: 5,
+    n_bytes: 7205,
+    n1n2_bytes: 7200,
+    vec_n_size_64: 901,
+    vec_n1n2_size_64: 900,
+    pk_bytes: 7237,
+    sk_bytes: 7333,
+    ct_bytes: 14421,
+    red_mask: (1u64 << 37) - 1,
+    barrett_recip: 74517, // floor(2^32 / 57637)
+    rs_poly: &RS_POLY_256,
+};
+
+// Maximum sizes for stack-allocated arrays.
+#[cfg(any(feature = "kgen", feature = "ecap", feature = "dcap"))]
+pub(crate) const MAX_N1: usize = 90;
+#[cfg(any(feature = "kgen", feature = "ecap", feature = "dcap"))]
+pub(crate) const MAX_DELTA: usize = 29;
+
+mod sealed {
+    /// Sealed trait preventing external implementations of [`HqcParams`](super::HqcParams).
+    pub trait Sealed {}
+}
+
+/// Trait defining an HQC parameter set.
+///
+/// Sealed — cannot be implemented outside this crate. Use one of the provided
+/// marker types: [`Hqc128Params`], [`Hqc192Params`], [`Hqc256Params`].
+pub trait HqcParams: sealed::Sealed + 'static {
+    /// Human-readable name (e.g. `"hqc128"`).
+    const NAME: &'static str;
+    /// Public key size in bytes.
+    const PK_BYTES: usize;
+    /// Secret key size in bytes.
+    const SK_BYTES: usize;
+    /// Ciphertext size in bytes.
+    const CT_BYTES: usize;
+    /// Shared secret size in bytes (always 32).
+    const SS_BYTES: usize = SS_BYTES;
+
+    /// Runtime parameter struct for internal operations.
+    #[doc(hidden)]
+    fn params() -> &'static HqcParameters;
+}
+
+/// HQC-128 parameter marker (NIST Level 1, 128-bit security).
+#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
+pub struct Hqc128Params;
+
+impl sealed::Sealed for Hqc128Params {}
+
+impl HqcParams for Hqc128Params {
+    const NAME: &'static str = "hqc128";
+    const PK_BYTES: usize = 2241;
+    const SK_BYTES: usize = 2321;
+    const CT_BYTES: usize = 4433;
+    fn params() -> &'static HqcParameters {
+        &HQC_128
+    }
+}
+
+/// HQC-192 parameter marker (NIST Level 3, 192-bit security).
+#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
+pub struct Hqc192Params;
+
+impl sealed::Sealed for Hqc192Params {}
+
+impl HqcParams for Hqc192Params {
+    const NAME: &'static str = "hqc192";
+    const PK_BYTES: usize = 4514;
+    const SK_BYTES: usize = 4602;
+    const CT_BYTES: usize = 8978;
+    fn params() -> &'static HqcParameters {
+        &HQC_192
+    }
+}
+
+/// HQC-256 parameter marker (NIST Level 5, 256-bit security).
+#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
+pub struct Hqc256Params;
+
+impl sealed::Sealed for Hqc256Params {}
+
+impl HqcParams for Hqc256Params {
+    const NAME: &'static str = "hqc256";
+    const PK_BYTES: usize = 7237;
+    const SK_BYTES: usize = 7333;
+    const CT_BYTES: usize = 14421;
+    fn params() -> &'static HqcParameters {
+        &HQC_256
+    }
+}
diff --git a/hqc-kem/src/pkcs8_impl.rs b/hqc-kem/src/pkcs8_impl.rs
new file mode 100644
index 00000000..5220ada7
--- /dev/null
+++ b/hqc-kem/src/pkcs8_impl.rs
@@ -0,0 +1,257 @@
+//! PKCS#8 encoding support for HQC-KEM keys.
+//!
+//! When the `pkcs8` feature is enabled, [`pkcs8::DecodePrivateKey`] is impl'd for
+//! [`DecapsulationKey`], and [`pkcs8::DecodePublicKey`] is impl'd for [`EncapsulationKey`].
+//!
+//! When both `pkcs8` and `alloc` features are enabled, [`EncodePrivateKey`] is impl'd
+//! for [`DecapsulationKey`], and [`EncodePublicKey`] is impl'd for [`EncapsulationKey`].
+
+pub use ::pkcs8::spki::AssociatedAlgorithmIdentifier;
+pub use const_oid::AssociatedOid;
+
+use crate::{
+    params::{Hqc128Params, Hqc192Params, Hqc256Params, HqcParams, SEED_BYTES},
+    types::{DecapsulationKey, EncapsulationKey},
+};
+use ::pkcs8::{
+    der::{
+        AnyRef, Reader, SliceReader, TagNumber,
+        asn1::{ContextSpecific, OctetStringRef},
+    },
+    spki,
+};
+
+#[cfg(feature = "alloc")]
+use ::pkcs8::der::{Encode, TagMode, asn1::BitStringRef};
+
+#[cfg(feature = "alloc")]
+use ::pkcs8::{EncodePrivateKey, EncodePublicKey};
+
+/// Tag number for the seed value (matches ml-kem convention).
+const SEED_TAG_NUMBER: TagNumber = TagNumber(0);
+
+/// HQC seed serialized as ASN.1.
+type SeedString<'a> = ContextSpecific<&'a OctetStringRef>;
+
+// ---------------------------------------------------------------------------
+// Provisional OIDs for HQC-KEM
+//
+// FIPS 207 does not yet have assigned OIDs. These are provisional placeholders
+// in the NIST KEM arc (2.16.840.1.101.3.4.4.x). ML-KEM uses .1/.2/.3.
+// These WILL change when NIST assigns official OIDs.
+// ---------------------------------------------------------------------------
+
+impl AssociatedOid for Hqc128Params {
+    const OID: ::pkcs8::ObjectIdentifier =
+        ::pkcs8::ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.4.4");
+}
+
+impl AssociatedOid for Hqc192Params {
+    const OID: ::pkcs8::ObjectIdentifier =
+        ::pkcs8::ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.4.5");
+}
+
+impl AssociatedOid for Hqc256Params {
+    const OID: ::pkcs8::ObjectIdentifier =
+        ::pkcs8::ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.4.6");
+}
+
+// AssociatedAlgorithmIdentifier for parameter types
+
+impl AssociatedAlgorithmIdentifier for Hqc128Params {
+    type Params = AnyRef<'static>;
+    const ALGORITHM_IDENTIFIER: spki::AlgorithmIdentifier<Self::Params> =
+        spki::AlgorithmIdentifier {
+            oid: Self::OID,
+            parameters: None,
+        };
+}
+
+impl AssociatedAlgorithmIdentifier for Hqc192Params {
+    type Params = AnyRef<'static>;
+    const ALGORITHM_IDENTIFIER: spki::AlgorithmIdentifier<Self::Params> =
+        spki::AlgorithmIdentifier {
+            oid: Self::OID,
+            parameters: None,
+        };
+}
+
+impl AssociatedAlgorithmIdentifier for Hqc256Params {
+    type Params = AnyRef<'static>;
+    const ALGORITHM_IDENTIFIER: spki::AlgorithmIdentifier<Self::Params> =
+        spki::AlgorithmIdentifier {
+            oid: Self::OID,
+            parameters: None,
+        };
+}
+
+// AssociatedAlgorithmIdentifier for key types (delegating to P)
+
+impl<P> AssociatedAlgorithmIdentifier for EncapsulationKey<P>
+where
+    P: HqcParams + AssociatedAlgorithmIdentifier<Params = AnyRef<'static>>,
+{
+    type Params = P::Params;
+    const ALGORITHM_IDENTIFIER: spki::AlgorithmIdentifier<Self::Params> = P::ALGORITHM_IDENTIFIER;
+}
+
+impl<P> AssociatedAlgorithmIdentifier for DecapsulationKey<P>
+where
+    P: HqcParams + AssociatedAlgorithmIdentifier<Params = AnyRef<'static>>,
+{
+    type Params = P::Params;
+    const ALGORITHM_IDENTIFIER: spki::AlgorithmIdentifier<Self::Params> = P::ALGORITHM_IDENTIFIER;
+}
+
+// ---------------------------------------------------------------------------
+// EncodePublicKey (requires alloc)
+// ---------------------------------------------------------------------------
+
+#[cfg(feature = "alloc")]
+impl<P> EncodePublicKey for EncapsulationKey<P>
+where
+    P: HqcParams + AssociatedAlgorithmIdentifier<Params = AnyRef<'static>>,
+{
+    fn to_public_key_der(&self) -> spki::Result<pkcs8::Document> {
+        let public_key = self.as_ref();
+        let subject_public_key = BitStringRef::new(0, public_key)?;
+
+        ::pkcs8::SubjectPublicKeyInfo {
+            algorithm: P::ALGORITHM_IDENTIFIER,
+            subject_public_key,
+        }
+        .try_into()
+    }
+}
+
+// ---------------------------------------------------------------------------
+// DecodePublicKey (via TryFrom<SubjectPublicKeyInfoRef>)
+// ---------------------------------------------------------------------------
+
+impl<P> TryFrom<::pkcs8::SubjectPublicKeyInfoRef<'_>> for EncapsulationKey<P>
+where
+    P: HqcParams + AssociatedAlgorithmIdentifier<Params = AnyRef<'static>>,
+{
+    type Error = spki::Error;
+
+    fn try_from(spki: ::pkcs8::SubjectPublicKeyInfoRef<'_>) -> Result<Self, spki::Error> {
+        if spki.algorithm.oid != P::ALGORITHM_IDENTIFIER.oid {
+            return Err(spki::Error::OidUnknown {
+                oid: P::ALGORITHM_IDENTIFIER.oid,
+            });
+        }
+
+        let bytes = spki
+            .subject_public_key
+            .as_bytes()
+            .ok_or(spki::Error::KeyMalformed)?;
+
+        if bytes.len() != P::PK_BYTES {
+            return Err(spki::Error::KeyMalformed);
+        }
+
+        Ok(EncapsulationKey::from_vec(bytes.to_vec()))
+    }
+}
+
+// ---------------------------------------------------------------------------
+// EncodePrivateKey (requires alloc)
+// ---------------------------------------------------------------------------
+
+#[cfg(feature = "alloc")]
+impl<P> EncodePrivateKey for DecapsulationKey<P>
+where
+    P: HqcParams + AssociatedAlgorithmIdentifier<Params = AnyRef<'static>>,
+{
+    fn to_pkcs8_der(&self) -> ::pkcs8::Result<pkcs8::SecretDocument> {
+        let sk = self.as_ref();
+        let seed = &sk[sk.len() - SEED_BYTES..];
+
+        let seed_der = SeedString {
+            tag_mode: TagMode::Implicit,
+            tag_number: SEED_TAG_NUMBER,
+            value: OctetStringRef::new(seed)?,
+        }
+        .to_der()?;
+
+        let private_key = OctetStringRef::new(&seed_der)?;
+        let private_key_info = pkcs8::PrivateKeyInfoRef::new(P::ALGORITHM_IDENTIFIER, private_key);
+        pkcs8::SecretDocument::encode_msg(&private_key_info).map_err(pkcs8::Error::Asn1)
+    }
+}
+
+// ---------------------------------------------------------------------------
+// DecodePrivateKey (via TryFrom<PrivateKeyInfoRef>)
+// ---------------------------------------------------------------------------
+
+impl<P> TryFrom<::pkcs8::PrivateKeyInfoRef<'_>> for DecapsulationKey<P>
+where
+    P: HqcParams + AssociatedAlgorithmIdentifier<Params = AnyRef<'static>>,
+{
+    type Error = ::pkcs8::Error;
+
+    fn try_from(private_key_info_ref: ::pkcs8::PrivateKeyInfoRef<'_>) -> Result<Self, Self::Error> {
+        let _ = private_key_info_ref
+            .algorithm
+            .assert_algorithm_oid(P::ALGORITHM_IDENTIFIER.oid)?;
+
+        let mut reader = SliceReader::new(private_key_info_ref.private_key.as_bytes())?;
+        let seed_string = SeedString::decode_implicit(&mut reader, SEED_TAG_NUMBER)?
+            .ok_or(pkcs8::KeyError::Invalid)?;
+        let seed: [u8; SEED_BYTES] = seed_string
+            .value
+            .as_bytes()
+            .try_into()
+            .map_err(|_| pkcs8::KeyError::Invalid)?;
+        reader.finish()?;
+
+        let (_pk, sk) = crate::kem::keygen_deterministic(&seed, P::params());
+        Ok(DecapsulationKey::from_vec(sk))
+    }
+}
+
+// ---------------------------------------------------------------------------
+// Tests
+// ---------------------------------------------------------------------------
+
+#[cfg(all(test, feature = "alloc"))]
+mod tests {
+    use super::*;
+    use ::pkcs8::{DecodePrivateKey, DecodePublicKey, EncodePrivateKey, EncodePublicKey};
+
+    macro_rules! pkcs8_roundtrip_test {
+        ($name:ident, $params:ty) => {
+            #[test]
+            fn $name() {
+                let mut rng = rand::rng();
+                let (ek, dk) = crate::HqcKem::<$params>::generate_key(&mut rng);
+
+                // Public key roundtrip
+                let pk_der = ek.to_public_key_der().expect("encode public key");
+                let ek2 = EncapsulationKey::<$params>::from_public_key_der(pk_der.as_bytes())
+                    .expect("decode public key");
+                assert_eq!(ek.as_ref(), ek2.as_ref());
+
+                // Private key roundtrip
+                let sk_der = dk.to_pkcs8_der().expect("encode private key");
+                let dk2 = DecapsulationKey::<$params>::from_pkcs8_der(sk_der.as_bytes())
+                    .expect("decode private key");
+
+                // Verify deterministic reconstruction: same EK
+                assert_eq!(
+                    dk.encapsulation_key().as_ref(),
+                    dk2.encapsulation_key().as_ref()
+                );
+
+                // Verify encaps/decaps works with reconstructed keys
+                let (ct, ss1) = ek2.encapsulate(&mut rng);
+                let ss2 = dk2.decapsulate(&ct);
+                assert_eq!(ss1, ss2);
+            }
+        };
+    }
+
+    pkcs8_roundtrip_test!(pkcs8_roundtrip_128, Hqc128Params);
+    pkcs8_roundtrip_test!(pkcs8_roundtrip_192, Hqc192Params);
+    pkcs8_roundtrip_test!(pkcs8_roundtrip_256, Hqc256Params);
+}
diff --git a/hqc-kem/src/pke.rs b/hqc-kem/src/pke.rs
new file mode 100644
index 00000000..ae97f1f3
--- /dev/null
+++ b/hqc-kem/src/pke.rs
@@ -0,0 +1,154 @@
+/// HQC Public Key Encryption (PKE) operations.
+///
+/// v5.0.0 PKE:
+/// - keygen: I(seed_pke) → (seed_dk, seed_ek), sample y,x via rej, s = x + h*y
+/// - encrypt: sample r2,e,r1 via mod from theta, u = r1 + h*r2, v = encode(m) + s*r2 + e
+/// - decrypt: y from seed_dk, v - u*y, decode
+use crate::code;
+use crate::params::{HqcParameters, SEED_BYTES};
+use crate::poly;
+use crate::sampling;
+use crate::shake::{self, SeedExpander};
+use zeroize::Zeroize;
+
+/// PKE key generation from a PKE seed.
+///
+/// Returns (ek_pke, dk_pke) where:
+/// - ek_pke = seed_pke_ek || s_bytes (pk_bytes total)
+/// - dk_pke = seed_pke_dk (SEED_BYTES)
+pub(crate) fn pke_keygen(seed_pke: &[u8], p: &HqcParameters) -> (Vec<u8>, Vec<u8>) {
+    // I function: SHA3-512(seed_pke || 0x02) → 64 bytes
+    let mut i_res = shake::hash_i(seed_pke);
+    let seed_pke_dk = &i_res[..SEED_BYTES];
+    let seed_pke_ek = &i_res[SEED_BYTES..2 * SEED_BYTES];
+
+    // Sample y, x from dk seed using rejection sampling
+    let mut ctx_dk = SeedExpander::new(seed_pke_dk);
+    let mut y = vec![0u64; p.vec_n_size_64];
+    let mut x = vec![0u64; p.vec_n_size_64];
+    sampling::sample_fixed_wt_rej(&mut ctx_dk, &mut y, p.w, p);
+    sampling::sample_fixed_wt_rej(&mut ctx_dk, &mut x, p.w, p);
+
+    // Sample h from ek seed
+    let mut ctx_ek = SeedExpander::new(seed_pke_ek);
+    let mut h = vec![0u64; p.vec_n_size_64];
+    sampling::sample_vect(&mut ctx_ek, &mut h, p);
+
+    // s = x + h*y
+    let mut s = vec![0u64; p.vec_n_size_64];
+    let mut tmp = vec![0u64; p.vec_n_size_64];
+    poly::vect_mul(&mut tmp, &y, &h, p);
+    poly::vect_add(&mut s, &x, &tmp, p.vec_n_size_64);
+
+    // Zeroize secret vectors — no longer needed
+    y.zeroize();
+    x.zeroize();
+
+    // ek_pke = seed_pke_ek || s_bytes
+    let mut ek_pke = vec![0u8; p.pk_bytes];
+    ek_pke[..SEED_BYTES].copy_from_slice(seed_pke_ek);
+    poly::store8_arr(&mut ek_pke[SEED_BYTES..], &s);
+
+    // dk_pke = seed_pke_dk
+    let dk_pke = seed_pke_dk.to_vec();
+
+    // Zeroize I function result (contains secret dk seed)
+    i_res.zeroize();
+
+    (ek_pke, dk_pke)
+}
+
+/// PKE encryption.
+///
+/// Returns ciphertext bytes: u_bytes || v_bytes (n_bytes + n1n2_bytes).
+pub(crate) fn pke_encrypt(ek_pke: &[u8], m: &[u8], theta: &[u8], p: &HqcParameters) -> Vec<u8> {
+    // Parse ek_pke
+    let seed_pke_ek = &ek_pke[..SEED_BYTES];
+
+    // Regenerate h from seed
+    let mut ctx_ek = SeedExpander::new(seed_pke_ek);
+    let mut h = vec![0u64; p.vec_n_size_64];
+    sampling::sample_vect(&mut ctx_ek, &mut h, p);
+
+    // Load s from ek_pke
+    let mut s = vec![0u64; p.vec_n_size_64];
+    poly::load8_arr(&mut s, &ek_pke[SEED_BYTES..]);
+
+    // Sample r2, e, r1 from theta using mod sampling
+    let mut ctx_th = SeedExpander::new(theta);
+    let mut r2 = vec![0u64; p.vec_n_size_64];
+    let mut e = vec![0u64; p.vec_n_size_64];
+    let mut r1 = vec![0u64; p.vec_n_size_64];
+    sampling::sample_fixed_wt_mod(&mut ctx_th, &mut r2, p.w_r, p);
+    sampling::sample_fixed_wt_mod(&mut ctx_th, &mut e, p.w_e, p);
+    sampling::sample_fixed_wt_mod(&mut ctx_th, &mut r1, p.w_r, p);
+
+    // u = r1 + h*r2
+    let mut u = vec![0u64; p.vec_n_size_64];
+    let mut tmp = vec![0u64; p.vec_n_size_64];
+    poly::vect_mul(&mut tmp, &r2, &h, p);
+    poly::vect_add(&mut u, &r1, &tmp, p.vec_n_size_64);
+
+    // cm = encode(m) - encoded into n1n2 bits
+    let mut cm = vec![0u64; p.vec_n1n2_size_64];
+    code::code_encode(&mut cm, m, p);
+
+    // v_full = cm + s*r2 + e (in n-bit space, then truncate to n1n2)
+    let mut sr2 = vec![0u64; p.vec_n_size_64];
+    poly::vect_mul(&mut sr2, &r2, &s, p);
+
+    // Work in n-bit space: extend cm and add
+    let mut v_full = vec![0u64; p.vec_n_size_64];
+    // Copy cm into v_full (n1n2 <= n)
+    poly::vect_resize(&mut v_full, p.n, &cm, p.n1n2);
+    poly::vect_add_assign(&mut v_full, &sr2, p.vec_n_size_64);
+    poly::vect_add_assign(&mut v_full, &e, p.vec_n_size_64);
+
+    // Truncate v to n1n2 bits
+    let mut v = vec![0u64; p.vec_n1n2_size_64];
+    poly::vect_resize(&mut v, p.n1n2, &v_full, p.n);
+
+    // Serialize: u_bytes || v_bytes
+    let mut ct = vec![0u8; p.n_bytes + p.n1n2_bytes];
+    poly::store8_arr(&mut ct[..p.n_bytes], &u);
+    poly::store8_arr(&mut ct[p.n_bytes..], &v);
+
+    ct
+}
+
+/// PKE decryption.
+///
+/// Returns decrypted message bytes (k bytes).
+pub(crate) fn pke_decrypt(dk_pke: &[u8], c_pke: &[u8], p: &HqcParameters) -> Vec<u8> {
+    // Regenerate y from dk seed
+    let mut ctx_dk = SeedExpander::new(&dk_pke[..SEED_BYTES]);
+    let mut y = vec![0u64; p.vec_n_size_64];
+    sampling::sample_fixed_wt_rej(&mut ctx_dk, &mut y, p.w, p);
+
+    // Parse ciphertext
+    let mut u = vec![0u64; p.vec_n_size_64];
+    let mut v = vec![0u64; p.vec_n1n2_size_64];
+    poly::load8_arr(&mut u, &c_pke[..p.n_bytes]);
+    poly::load8_arr(&mut v, &c_pke[p.n_bytes..p.n_bytes + p.n1n2_bytes]);
+
+    // Compute v - u*y (XOR in GF(2))
+    let mut uy = vec![0u64; p.vec_n_size_64];
+    poly::vect_mul(&mut uy, &y, &u, p);
+
+    // Zeroize secret vector y — no longer needed
+    y.zeroize();
+
+    // Extend v to n bits, XOR with uy, truncate back
+    let mut v_full = vec![0u64; p.vec_n_size_64];
+    poly::vect_resize(&mut v_full, p.n, &v, p.n1n2);
+    poly::vect_add_assign(&mut v_full, &uy, p.vec_n_size_64);
+
+    // Truncate to n1n2 bits for decoding
+    let mut cm = vec![0u64; p.vec_n1n2_size_64];
+    poly::vect_resize(&mut cm, p.n1n2, &v_full, p.n);
+
+    // Decode
+    let mut m = vec![0u8; p.k];
+    code::code_decode(&mut m, &cm, p);
+    m
+}
diff --git a/hqc-kem/src/poly.rs b/hqc-kem/src/poly.rs
new file mode 100644
index 00000000..e4ce631e
--- /dev/null
+++ b/hqc-kem/src/poly.rs
@@ -0,0 +1,296 @@
+/// Binary polynomial operations in Z_2[X]/(X^n - 1).
+///
+/// Polynomials are stored as arrays of u64, where each bit is a coefficient.
+/// Uses Karatsuba multiplication for efficiency.
+use crate::params::HqcParameters;
+
+/// Polynomial addition: o = v1 XOR v2.
+#[inline]
+pub(crate) fn vect_add(o: &mut [u64], v1: &[u64], v2: &[u64], size: usize) {
+    for i in 0..size {
+        o[i] = v1[i] ^ v2[i];
+    }
+}
+
+/// In-place polynomial addition: v ^= rhs.
+#[inline]
+pub(crate) fn vect_add_assign(v: &mut [u64], rhs: &[u64], size: usize) {
+    for i in 0..size {
+        v[i] ^= rhs[i];
+    }
+}
+
+/// Constant-time byte comparison. Returns 0 if equal, non-zero otherwise.
+pub(crate) fn vect_compare(v1: &[u8], v2: &[u8]) -> u8 {
+    let mut r: u16 = 0x0100;
+    for i in 0..v1.len().min(v2.len()) {
+        r |= (v1[i] ^ v2[i]) as u16;
+    }
+    ((r.wrapping_sub(1)) >> 8) as u8
+}
+
+/// Carry-less multiplication of two 64-bit words.
+///
+/// Uses PCLMULQDQ on x86-64 when available, otherwise constant-time software fallback.
+#[inline]
+fn base_mul(a: u64, b: u64) -> [u64; 2] {
+    #[cfg(target_arch = "x86_64")]
+    {
+        if std::is_x86_feature_detected!("pclmulqdq") {
+            return unsafe { base_mul_pclmul(a, b) };
+        }
+    }
+    base_mul_soft(a, b)
+}
+
+#[cfg(target_arch = "x86_64")]
+#[target_feature(enable = "pclmulqdq")]
+#[inline]
+unsafe fn base_mul_pclmul(a: u64, b: u64) -> [u64; 2] {
+    use std::arch::x86_64::*;
+    unsafe {
+        let va = _mm_set_epi64x(0, a as i64);
+        let vb = _mm_set_epi64x(0, b as i64);
+        let r = _mm_clmulepi64_si128(va, vb, 0x00);
+        let mut result = [0u64; 2];
+        _mm_storeu_si128(result.as_mut_ptr() as *mut __m128i, r);
+        result
+    }
+}
+
+/// Software carry-less multiplication (constant-time).
+#[inline]
+fn base_mul_soft(a: u64, b: u64) -> [u64; 2] {
+    let mut h: u64 = 0;
+    let mut l: u64;
+    let mut g: u64;
+    let mut u = [0u64; 16];
+
+    // Precompute small multiples of b (with top 4 bits masked)
+    u[0] = 0;
+    u[1] = b & ((1u64 << 60) - 1);
+    u[2] = u[1] << 1;
+    u[3] = u[2] ^ u[1];
+    u[4] = u[2] << 1;
+    u[5] = u[4] ^ u[1];
+    u[6] = u[3] << 1;
+    u[7] = u[6] ^ u[1];
+    u[8] = u[4] << 1;
+    u[9] = u[8] ^ u[1];
+    u[10] = u[5] << 1;
+    u[11] = u[10] ^ u[1];
+    u[12] = u[6] << 1;
+    u[13] = u[12] ^ u[1];
+    u[14] = u[7] << 1;
+    u[15] = u[14] ^ u[1];
+
+    // First nibble
+    g = 0;
+    let tmp1 = a & 0x0f;
+    for i in 0..16u64 {
+        let tmp2 = tmp1.wrapping_sub(i);
+        let mask = 0u64.wrapping_sub(1u64.wrapping_sub((tmp2 | tmp2.wrapping_neg()) >> 63));
+        g ^= u[i as usize] & mask;
+    }
+    l = g;
+
+    // Remaining nibbles
+    for shift in (4..64).step_by(4) {
+        g = 0;
+        let tmp1 = (a >> shift) & 0x0f;
+        for j in 0..16u64 {
+            let tmp2 = tmp1.wrapping_sub(j);
+            let mask = 0u64.wrapping_sub(1u64.wrapping_sub((tmp2 | tmp2.wrapping_neg()) >> 63));
+            g ^= u[j as usize] & mask;
+        }
+        l ^= g << shift;
+        h ^= g >> (64 - shift);
+    }
+
+    // Handle top 4 bits of b
+    let masks = [
+        0u64.wrapping_sub((b >> 60) & 1),
+        0u64.wrapping_sub((b >> 61) & 1),
+        0u64.wrapping_sub((b >> 62) & 1),
+        0u64.wrapping_sub((b >> 63) & 1),
+    ];
+
+    l ^= (a << 60) & masks[0];
+    h ^= (a >> 4) & masks[0];
+    l ^= (a << 61) & masks[1];
+    h ^= (a >> 3) & masks[1];
+    l ^= (a << 62) & masks[2];
+    h ^= (a >> 2) & masks[2];
+    l ^= (a << 63) & masks[3];
+    h ^= (a >> 1) & masks[3];
+
+    [l, h]
+}
+
+#[inline]
+fn karatsuba_add1(
+    alh: &mut [u64],
+    blh: &mut [u64],
+    a: &[u64],
+    b: &[u64],
+    size_l: usize,
+    size_h: usize,
+) {
+    for i in 0..size_h {
+        alh[i] = a[i] ^ a[i + size_l];
+        blh[i] = b[i] ^ b[i + size_l];
+    }
+    if size_h < size_l {
+        alh[size_h] = a[size_h];
+        blh[size_h] = b[size_h];
+    }
+}
+
+#[inline]
+fn karatsuba_add2(o: &mut [u64], tmp1: &mut [u64], tmp2: &[u64], size_l: usize, size_h: usize) {
+    for i in 0..(2 * size_l) {
+        tmp1[i] ^= o[i];
+    }
+    for i in 0..(2 * size_h) {
+        tmp1[i] ^= tmp2[i];
+    }
+    for i in 0..(2 * size_l) {
+        o[i + size_l] ^= tmp1[i];
+    }
+}
+
+/// Recursive Karatsuba multiplication.
+///
+/// Stack layout per level: [alh(size_l) | blh(size_l) | tmp1(2*size_l) | copies(2*size_l) | deeper...]
+/// Total local = 6*size_l per level. The 8*vec_n pre-allocation is sufficient for all levels.
+fn karatsuba(o: &mut [u64], a: &[u64], b: &[u64], size: usize, stack: &mut [u64]) {
+    if size == 1 {
+        let c = base_mul(a[0], b[0]);
+        o[0] = c[0];
+        o[1] = c[1];
+        return;
+    }
+
+    let size_h = size / 2;
+    let size_l = size.div_ceil(2);
+
+    // Split stack: 6*size_l for this level, rest for recursion
+    let (local, stack_rest) = stack.split_at_mut(6 * size_l);
+
+    // local layout: [alh | blh | tmp1 | copies]
+    let (alh_blh, tmp1_copies) = local.split_at_mut(2 * size_l);
+    let (tmp1_part, copies_part) = tmp1_copies.split_at_mut(2 * size_l);
+
+    karatsuba(o, a, b, size_l, stack_rest);
+    karatsuba(
+        &mut o[2 * size_l..],
+        &a[size_l..],
+        &b[size_l..],
+        size_h,
+        stack_rest,
+    );
+
+    {
+        let (alh_part, blh_part) = alh_blh.split_at_mut(size_l);
+        karatsuba_add1(alh_part, blh_part, a, b, size_l, size_h);
+    }
+
+    // Copy alh/blh into copies region so we can pass tmp1 as mutable output
+    let (alh, blh) = alh_blh.split_at(size_l);
+    let (alh_copy, blh_copy) = copies_part.split_at_mut(size_l);
+    alh_copy[..size_l].copy_from_slice(alh);
+    blh_copy[..size_l].copy_from_slice(&blh[..size_l]);
+
+    // Clear tmp1 before writing
+    for v in tmp1_part[..2 * size_l].iter_mut() {
+        *v = 0;
+    }
+    karatsuba(
+        tmp1_part,
+        &alh_copy[..size_l],
+        &blh_copy[..size_l],
+        size_l,
+        stack_rest,
+    );
+
+    // Copy tmp2 (in o[2*size_l..]) into copies region to avoid aliasing
+    let tmp2_len = 2 * size_h;
+    copies_part[..tmp2_len].copy_from_slice(&o[2 * size_l..2 * size_l + tmp2_len]);
+
+    karatsuba_add2(o, tmp1_part, &copies_part[..tmp2_len], size_l, size_h);
+}
+
+/// Reduce polynomial modulo X^n - 1.
+fn reduce(o: &mut [u64], a: &[u64], n: usize, vec_n_size_64: usize) {
+    let shift = n & 0x3f;
+    for i in 0..vec_n_size_64 {
+        let r = a[i + vec_n_size_64 - 1] >> shift;
+        let carry = if i + vec_n_size_64 < a.len() {
+            a[i + vec_n_size_64] << (64 - shift)
+        } else {
+            0
+        };
+        o[i] = a[i] ^ r ^ carry;
+    }
+}
+
+/// Multiply two polynomials modulo X^n - 1.
+pub(crate) fn vect_mul(o: &mut [u64], v1: &[u64], v2: &[u64], p: &HqcParameters) {
+    let vec_n = p.vec_n_size_64;
+    let mut stack = vec![0u64; vec_n << 3];
+    let mut o_karat = vec![0u64; vec_n << 1];
+
+    karatsuba(&mut o_karat, &v1[..vec_n], &v2[..vec_n], vec_n, &mut stack);
+    reduce(o, &o_karat, p.n, vec_n);
+    o[vec_n - 1] &= p.red_mask;
+}
+
+/// Load byte array into u64 array (little-endian).
+#[inline]
+pub(crate) fn load8_arr(out: &mut [u64], inp: &[u8]) {
+    let full_chunks = inp.len() / 8;
+    let count = full_chunks.min(out.len());
+    for i in 0..count {
+        let mut buf = [0u8; 8];
+        buf.copy_from_slice(&inp[i * 8..(i + 1) * 8]);
+        out[i] = u64::from_le_bytes(buf);
+    }
+    let rem = inp.len() - count * 8;
+    if rem > 0 && count < out.len() {
+        let mut buf = [0u8; 8];
+        buf[..rem].copy_from_slice(&inp[count * 8..]);
+        out[count] = u64::from_le_bytes(buf);
+    }
+}
+
+/// Store u64 array into byte array (little-endian).
+#[inline]
+pub(crate) fn store8_arr(out: &mut [u8], inp: &[u64]) {
+    let full_chunks = out.len() / 8;
+    let count = full_chunks.min(inp.len());
+    for i in 0..count {
+        out[i * 8..(i + 1) * 8].copy_from_slice(&inp[i].to_le_bytes());
+    }
+    let rem = out.len() - count * 8;
+    if rem > 0 && count < inp.len() {
+        let bytes = inp[count].to_le_bytes();
+        out[count * 8..].copy_from_slice(&bytes[..rem]);
+    }
+}
+
+/// Resize vector: copy and potentially truncate.
+pub(crate) fn vect_resize(o: &mut [u64], size_o: usize, v: &[u64], size_v: usize) {
+    if size_o < size_v {
+        let n1n2_64 = size_o.div_ceil(64);
+        let copy_words = n1n2_64.min(o.len()).min(v.len());
+        o[..copy_words].copy_from_slice(&v[..copy_words]);
+        let bits_in_last = size_o % 64;
+        if bits_in_last != 0 && n1n2_64 > 0 && n1n2_64 - 1 < o.len() {
+            o[n1n2_64 - 1] &= (1u64 << bits_in_last) - 1;
+        }
+    } else {
+        let words = size_v.div_ceil(64);
+        let copy_words = words.min(o.len()).min(v.len());
+        o[..copy_words].copy_from_slice(&v[..copy_words]);
+    }
+}
diff --git a/hqc-kem/src/reed_muller.rs b/hqc-kem/src/reed_muller.rs
new file mode 100644
index 00000000..46be4897
--- /dev/null
+++ b/hqc-kem/src/reed_muller.rs
@@ -0,0 +1,149 @@
+/// Reed-Muller RM(1,7) encoding and decoding with repetition.
+///
+/// Each byte is encoded into a 128-bit RM(1,7) codeword, then repeated
+/// `mult` times to fill `n2` bits per symbol.
+use crate::params::HqcParameters;
+
+/// Number of repetitions for a given parameter set.
+fn multiplicity(p: &HqcParameters) -> usize {
+    p.n2.div_ceil(128)
+}
+
+/// Copy bit 0 of x into all 32 bits.
+#[inline]
+fn bit0mask(x: u32) -> u32 {
+    0u32.wrapping_sub(x & 1)
+}
+
+/// Encode a single byte into a 128-bit RM(1,7) codeword (two u64s).
+fn encode_single(message: u8) -> [u64; 2] {
+    // bit 7 flips all bits
+    let mut first_word: u32 = bit0mask((message >> 7) as u32);
+    first_word ^= bit0mask(message as u32) & 0xAAAAAAAA;
+    first_word ^= bit0mask((message >> 1) as u32) & 0xCCCCCCCC;
+    first_word ^= bit0mask((message >> 2) as u32) & 0xF0F0F0F0;
+    first_word ^= bit0mask((message >> 3) as u32) & 0xFF00FF00;
+    first_word ^= bit0mask((message >> 4) as u32) & 0xFFFF0000;
+
+    let mut cword = [0u64; 2];
+    cword[0] = first_word as u64;
+
+    // bit 5 flips entries 1 and 3; bit 6 flips entries 2 and 3
+    first_word ^= bit0mask((message >> 5) as u32);
+    cword[0] |= (first_word as u64) << 32;
+
+    first_word ^= bit0mask((message >> 6) as u32);
+    cword[1] = (first_word as u64) << 32;
+
+    first_word ^= bit0mask((message >> 5) as u32);
+    cword[1] |= first_word as u64;
+
+    cword
+}
+
+/// Hadamard transform using a flag to track which buffer has the result.
+fn hadamard_transform(expanded: &mut [i16; 128], transform: &mut [i16; 128]) {
+    // Copy expanded to a working buffer
+    let mut buf_a = *expanded;
+    let mut buf_b = [0i16; 128];
+
+    let mut src = &mut buf_a;
+    let mut dst = &mut buf_b;
+
+    for _pass in 0..7 {
+        for i in 0..64 {
+            dst[i] = src[2 * i].wrapping_add(src[2 * i + 1]);
+            dst[i + 64] = src[2 * i].wrapping_sub(src[2 * i + 1]);
+        }
+        core::mem::swap(&mut src, &mut dst);
+    }
+    // After 7 passes, result is in `src`
+    transform.copy_from_slice(src);
+}
+
+/// Sum repeated codewords into 128 accumulators.
+fn expand_and_sum(dest: &mut [i16; 128], src: &[u64], mult: usize) {
+    // First copy
+    for part in 0..2 {
+        for bit in 0..64 {
+            dest[part * 64 + bit] = ((src[part] >> bit) & 1) as i16;
+        }
+    }
+    // Sum remaining copies
+    for copy in 1..mult {
+        for part in 0..2 {
+            if 2 * copy + part < src.len() {
+                for bit in 0..64 {
+                    dest[part * 64 + bit] += ((src[2 * copy + part] >> bit) & 1) as i16;
+                }
+            }
+        }
+    }
+}
+
+/// Find the peak in the Hadamard transform (constant-time).
+fn find_peaks(transform: &[i16; 128], _mult: usize) -> u8 {
+    let mut peak_abs: u16 = 0;
+    let mut peak: i16 = 0;
+    let mut pos: u16 = 0;
+
+    for i in 0..128u16 {
+        let t = transform[i as usize];
+        // Branchless absolute value: avoids timing leak on Hadamard coefficients
+        let mask = t >> 15; // arithmetic right shift: -1 if negative, 0 if non-negative
+        let abs_t = ((t ^ mask).wrapping_sub(mask)) as u16;
+
+        // Update if this abs is strictly greater (constant time)
+        let mask = 0u16.wrapping_sub((peak_abs.wrapping_sub(abs_t)) >> 15); // mask = 0xFFFF if peak_abs < abs_t
+        peak = (peak & !(mask as i16)) | (t & (mask as i16));
+        pos = (pos & !mask) | (i & mask);
+        peak_abs = (peak_abs & !mask) | (abs_t & mask);
+    }
+
+    // Set bit 7 if peak is positive (>=0)
+    let positive = 0u16.wrapping_sub(1u16.wrapping_sub((peak as u16) >> 15)); // 0xFFFF if positive
+    pos |= 128 & positive;
+
+    pos as u8
+}
+
+/// Encode message bytes into concatenated RM codewords.
+///
+/// Each byte of `msg` (length n1) is RM-encoded into `mult` repetitions of
+/// 128-bit codewords, producing n1*n2 bits total in `cdw`.
+pub(crate) fn reed_muller_encode(cdw: &mut [u64], msg: &[u8], p: &HqcParameters) {
+    let mult = multiplicity(p);
+    for (i, &byte) in msg.iter().enumerate().take(p.n1) {
+        let cword = encode_single(byte);
+        // Write first codeword
+        let base = 2 * i * mult;
+        if base + 1 < cdw.len() {
+            cdw[base] = cword[0];
+            cdw[base + 1] = cword[1];
+        }
+        // Copy to remaining repetitions
+        for copy in 1..mult {
+            let dst = base + 2 * copy;
+            if dst + 1 < cdw.len() {
+                cdw[dst] = cword[0];
+                cdw[dst + 1] = cword[1];
+            }
+        }
+    }
+}
+
+/// Decode concatenated RM codewords into message bytes.
+pub(crate) fn reed_muller_decode(msg: &mut [u8], cdw: &[u64], p: &HqcParameters) {
+    let mult = multiplicity(p);
+    let mut expanded = [0i16; 128];
+    let mut transform = [0i16; 128];
+
+    for (i, byte) in msg.iter_mut().enumerate().take(p.n1) {
+        let base = 2 * i * mult;
+        expand_and_sum(&mut expanded, &cdw[base..], mult);
+        hadamard_transform(&mut expanded, &mut transform);
+        // Fix first entry: subtract 64 * mult
+        transform[0] = transform[0].wrapping_sub((64 * mult) as i16);
+        *byte = find_peaks(&transform, mult);
+    }
+}
diff --git a/hqc-kem/src/reed_solomon.rs b/hqc-kem/src/reed_solomon.rs
new file mode 100644
index 00000000..7466f41f
--- /dev/null
+++ b/hqc-kem/src/reed_solomon.rs
@@ -0,0 +1,233 @@
+/// Reed-Solomon encoding and decoding over GF(2^8).
+///
+/// Systematic RS(n1, k) with error correction capability delta = (n1-k)/2.
+/// Generator polynomial roots: alpha^1, ..., alpha^(2*delta).
+use crate::fft;
+use crate::gf256::{GF_EXP, gf_inverse, gf_mul};
+use crate::params::{HqcParameters, MAX_DELTA, MAX_N1};
+
+/// Encode message into RS codeword (systematic).
+///
+/// Message bytes are placed at positions [n1-k..n1-1] (high end).
+/// Parity bytes at positions [0..n1-k-1] (low end).
+pub(crate) fn reed_solomon_encode(cdw: &mut [u8], msg: &[u8], p: &HqcParameters) {
+    // Clear output
+    cdw[..p.n1].fill(0);
+
+    for i in 0..p.k {
+        let gate_value = msg[p.k - 1 - i] ^ cdw[p.n1 - p.k - 1];
+
+        // Shift register: shift right by 1 and XOR with gate_value * g[k]
+        for k_idx in (1..p.n1 - p.k).rev() {
+            cdw[k_idx] =
+                cdw[k_idx - 1] ^ (gf_mul(gate_value as u16, p.rs_poly[k_idx] as u16) as u8);
+        }
+        cdw[0] = gf_mul(gate_value as u16, p.rs_poly[0] as u16) as u8;
+    }
+
+    // Copy message to high positions
+    cdw[p.n1 - p.k..p.n1].copy_from_slice(&msg[..p.k]);
+}
+
+/// Compute 2*delta syndromes.
+#[allow(clippy::needless_range_loop)] // indices used in arithmetic matching FIPS spec
+fn compute_syndromes(syndromes: &mut [u16], cdw: &[u8], p: &HqcParameters) {
+    for i in 0..(2 * p.delta) {
+        syndromes[i] = 0;
+        for j in 1..p.n1 {
+            // alpha^((i+1)*j)
+            let pow = ((i + 1) * j) % 255;
+            syndromes[i] ^= gf_mul(cdw[j] as u16, GF_EXP[pow]);
+        }
+        syndromes[i] ^= cdw[0] as u16;
+    }
+}
+
+/// Berlekamp algorithm: compute error locator polynomial sigma.
+///
+/// Constant-time implementation using mask-based updates.
+/// Returns degree of sigma.
+fn compute_elp(sigma: &mut [u16], syndromes: &[u16], p: &HqcParameters) -> u16 {
+    let max_delta = p.delta;
+    let mut deg_sigma: u16 = 0;
+    let mut deg_sigma_p: u16 = 0;
+    let mut sigma_copy = [0u16; MAX_DELTA + 1];
+    let mut x_sigma_p = [0u16; MAX_DELTA + 1];
+    x_sigma_p[1] = 1;
+    let mut pp: u16 = 0u16.wrapping_sub(1); // -1 in u16
+    let mut d_p: u16 = 1;
+    let mut d: u16 = syndromes[0];
+
+    sigma[0] = 1;
+
+    for mu in 0..(2 * max_delta) {
+        // Save sigma
+        let deg_sigma_copy_val = deg_sigma;
+        sigma_copy[..max_delta].copy_from_slice(&sigma[..max_delta]);
+
+        let dd = gf_mul(d, gf_inverse(d_p));
+
+        for i in 1..=(mu + 1).min(max_delta) {
+            sigma[i] ^= gf_mul(dd, x_sigma_p[i]);
+        }
+
+        let deg_x = (mu as u16).wrapping_sub(pp);
+        let deg_x_sigma_p = deg_x.wrapping_add(deg_sigma_p);
+
+        // mask1 = 0xFFFF if d != 0
+        let mask1 = 0u16.wrapping_sub((0u16.wrapping_sub(d)) >> 15);
+        // mask2 = 0xFFFF if deg_x_sigma_p > deg_sigma
+        let mask2 = 0u16.wrapping_sub(deg_sigma.wrapping_sub(deg_x_sigma_p) >> 15);
+        let mask12 = mask1 & mask2;
+
+        deg_sigma ^= mask12 & (deg_x_sigma_p ^ deg_sigma);
+
+        if mu == 2 * max_delta - 1 {
+            break;
+        }
+
+        pp ^= mask12 & ((mu as u16) ^ pp);
+        d_p ^= mask12 & (d ^ d_p);
+
+        for i in (1..=max_delta).rev() {
+            x_sigma_p[i] = (mask12 & sigma_copy[i - 1]) ^ (!mask12 & x_sigma_p[i - 1]);
+        }
+        x_sigma_p[0] = 0;
+
+        deg_sigma_p ^= mask12 & (deg_sigma_copy_val ^ deg_sigma_p);
+
+        d = syndromes[mu + 1];
+        for i in 1..=(mu + 1).min(max_delta) {
+            d ^= gf_mul(sigma[i], syndromes[mu + 1 - i]);
+        }
+    }
+
+    deg_sigma
+}
+
+/// Compute z polynomial for error value computation.
+fn compute_z_poly(z: &mut [u16], sigma: &[u16], degree: u16, syndromes: &[u16], delta: usize) {
+    z[0] = 1;
+
+    for i in 1..=delta {
+        let mask = 0u16.wrapping_sub(((i as u16).wrapping_sub(degree.wrapping_add(1))) >> 15);
+        z[i] = mask & sigma[i];
+    }
+
+    z[1] ^= syndromes[0];
+
+    for i in 2..=delta {
+        let mask = 0u16.wrapping_sub(((i as u16).wrapping_sub(degree.wrapping_add(1))) >> 15);
+        z[i] ^= mask & syndromes[i - 1];
+        for j in 1..i {
+            z[i] ^= mask & gf_mul(sigma[j], syndromes[i - j - 1]);
+        }
+    }
+}
+
+/// Compute error values using Forney's algorithm.
+#[allow(clippy::needless_range_loop)] // constant-time mask operations indexed across multiple arrays
+fn compute_error_values(error_values: &mut [u16], z: &[u16], error: &[u8; 256], p: &HqcParameters) {
+    let mut beta_j = [0u16; MAX_DELTA];
+    let mut e_j = [0u16; MAX_DELTA];
+
+    // Compute beta values (error locator field elements)
+    let mut delta_counter: u16 = 0;
+    for i in 0..p.n1 {
+        let found_mask =
+            0u16.wrapping_sub(((0i32.wrapping_sub(error[i] as i32)) as u32 >> 31) as u16);
+        let mut local_found: u16 = 0;
+
+        for j in 0..p.delta {
+            // Proper constant-time eq: both are u16
+            let diff = (j as u16) ^ delta_counter;
+            let zero_mask =
+                0u16.wrapping_sub(((diff as u32 | diff.wrapping_neg() as u32) >> 31) as u16);
+            let eq_mask2 = !zero_mask; // 0xFFFF if j == delta_counter
+
+            beta_j[j] ^= found_mask & eq_mask2 & GF_EXP[i];
+            local_found = local_found.wrapping_add(found_mask & eq_mask2 & 1);
+        }
+        delta_counter = delta_counter.wrapping_add(local_found);
+    }
+    let delta_real_value = delta_counter;
+
+    // Compute error values
+    for i in 0..p.delta {
+        let mut tmp1: u16 = 1;
+        let mut tmp2: u16 = 1;
+        let inverse = gf_inverse(beta_j[i]);
+        let mut inverse_power_j: u16 = 1;
+
+        for j in 1..=p.delta {
+            inverse_power_j = gf_mul(inverse_power_j, inverse);
+            tmp1 ^= gf_mul(inverse_power_j, z[j]);
+        }
+
+        for k in 1..p.delta {
+            let idx = (i + k) % p.delta;
+            tmp2 = gf_mul(tmp2, 1 ^ gf_mul(inverse, beta_j[idx]));
+        }
+
+        // mask1 = 0xFFFF if i < delta_real_value
+        let mask1 = 0u16.wrapping_sub(((i as u16).wrapping_sub(delta_real_value)) >> 15);
+        e_j[i] = mask1 & gf_mul(tmp1, gf_inverse(tmp2));
+    }
+
+    // Place error values at correct positions
+    delta_counter = 0;
+    for i in 0..p.n1 {
+        error_values[i] = 0;
+        let found_mask =
+            0u16.wrapping_sub(((0i32.wrapping_sub(error[i] as i32)) as u32 >> 31) as u16);
+        let mut local_found: u16 = 0;
+
+        for j in 0..p.delta {
+            let diff = (j as u16) ^ delta_counter;
+            let zero_mask =
+                0u16.wrapping_sub(((diff as u32 | diff.wrapping_neg() as u32) >> 31) as u16);
+            let eq_mask = !zero_mask;
+
+            error_values[i] ^= found_mask & eq_mask & e_j[j];
+            local_found = local_found.wrapping_add(found_mask & eq_mask & 1);
+        }
+        delta_counter = delta_counter.wrapping_add(local_found);
+    }
+}
+
+/// Decode RS codeword, correcting up to delta errors.
+///
+/// Modifies `cdw` in place, then extracts message from positions [2*delta..n1-1].
+pub(crate) fn reed_solomon_decode(msg: &mut [u8], cdw: &mut [u8], p: &HqcParameters) {
+    let mut syndromes = [0u16; 2 * MAX_DELTA];
+    let mut sigma = [0u16; 1 << 5]; // 2^MAX_FFT
+    let mut error = [0u8; 256]; // 2^PARAM_M
+    let mut z = [0u16; MAX_N1];
+    let mut error_values = [0u16; MAX_N1];
+
+    // Compute syndromes
+    compute_syndromes(&mut syndromes, cdw, p);
+
+    // Compute error locator polynomial
+    let deg = compute_elp(&mut sigma, &syndromes, p);
+
+    // Find roots via FFT
+    let mut w = [0u16; 256];
+    fft::fft(&mut w, &sigma, p.delta + 1, p.fft);
+    fft::fft_retrieve_error_poly(&mut error, &w);
+
+    // Compute z polynomial
+    compute_z_poly(&mut z, &sigma, deg, &syndromes, p.delta);
+
+    // Compute error values
+    compute_error_values(&mut error_values, &z, &error, p);
+
+    // Correct errors
+    for i in 0..p.n1 {
+        cdw[i] ^= error_values[i] as u8;
+    }
+
+    // Extract message (positions after parity bytes)
+    let parity_len = 2 * p.delta;
+    msg[..p.k].copy_from_slice(&cdw[parity_len..parity_len + p.k]);
+}
diff --git a/hqc-kem/src/sampling.rs b/hqc-kem/src/sampling.rs
new file mode 100644
index 00000000..f10e3f2e
--- /dev/null
+++ b/hqc-kem/src/sampling.rs
@@ -0,0 +1,146 @@
+/// Fixed-weight vector sampling.
+///
+/// Two methods per v5.0.0:
+/// - `sample_fixed_wt_mod`: modular mapping, used in encrypt (r2, e, r1).
+/// - `sample_fixed_wt_rej`: rejection sampling, used in keygen (y, x).
+/// - `sample_vect`: uniform random vector via XOF.
+use crate::params::HqcParameters;
+use crate::shake::SeedExpander;
+
+/// Sample a random binary vector of n bits via XOF.
+pub(crate) fn sample_vect(xof: &mut SeedExpander, v: &mut [u64], p: &HqcParameters) {
+    let mut rand_bytes = vec![0u8; p.n_bytes];
+    xof.get_bytes(&mut rand_bytes);
+    crate::poly::load8_arr(v, &rand_bytes);
+    if p.vec_n_size_64 > 0 {
+        v[p.vec_n_size_64 - 1] &= p.red_mask;
+    }
+}
+
+/// Sample a fixed-weight vector using modular mapping (v5.0.0 encrypt).
+///
+/// Reads 4*weight bytes from XOF (with alignment waste), maps each u32
+/// to position via multiplication-based reduction, deduplicates.
+pub(crate) fn sample_fixed_wt_mod(
+    xof: &mut SeedExpander,
+    v: &mut [u64],
+    weight: usize,
+    p: &HqcParameters,
+) {
+    let mut rand_bytes = vec![0u8; 4 * weight];
+    xof.get_bytes(&mut rand_bytes);
+
+    let mut pos = vec![0u32; weight];
+    for i in 0..weight {
+        let u = u32::from_le_bytes([
+            rand_bytes[4 * i],
+            rand_bytes[4 * i + 1],
+            rand_bytes[4 * i + 2],
+            rand_bytes[4 * i + 3],
+        ]);
+        // v5.0.0 modular mapping: pos = ((u * (n-i)) >> 32) + i
+        let n_minus_i = (p.n - i) as u64;
+        pos[i] = (((u as u64 * n_minus_i) >> 32) + i as u64) as u32;
+    }
+
+    // Dedup backwards (v5.0.0 style): for each i from wt-1 down to 1,
+    // check if any j < i has the same position. If so, set pos[i] = i.
+    for i in (1..weight).rev() {
+        let mut found = 0u32;
+        for j in 0..i {
+            // Constant-time equality check
+            let diff = pos[j] ^ pos[i];
+            let is_zero = (diff as u64 | (diff as u64).wrapping_neg()) >> 63; // 1 if non-zero
+            found |= 1u32.wrapping_sub(is_zero as u32); // 1 if zero (equal)
+        }
+        let mask = 0u32.wrapping_sub(found & 1);
+        pos[i] = (pos[i] & !mask) | (i as u32 & mask);
+    }
+
+    // Set bits in output vector
+    for i in 0..p.vec_n_size_64.min(v.len()) {
+        v[i] = 0;
+    }
+    for &position in pos.iter().take(weight) {
+        let idx = position as usize >> 6;
+        let bit = position as usize & 0x3f;
+        if idx < v.len() {
+            v[idx] |= 1u64 << bit;
+        }
+    }
+}
+
+/// Barrett reduction: val mod n using precomputed reciprocal.
+///
+/// Avoids variable-time `div` instruction. Uses `floor(2^32 / n)` as reciprocal.
+/// Requires val < n * (2^32 / n), which holds for val < 2^24 and n < 2^17.
+#[inline]
+fn barrett_reduce(val: u32, n: u32, reciprocal: u32) -> u32 {
+    let q = ((val as u64 * reciprocal as u64) >> 32) as u32;
+    let mut r = val.wrapping_sub(q.wrapping_mul(n));
+    // At most one correction needed: if r >= n, subtract n.
+    // Constant-time: mask is all-ones if r >= n, all-zeros otherwise.
+    let correction = n & 0u32.wrapping_sub((r >= n) as u32);
+    r = r.wrapping_sub(correction);
+    r
+}
+
+/// Sample a fixed-weight vector using rejection sampling (v5.0.0 keygen).
+///
+/// Reads 3*weight bytes per XOF chunk (matching reference implementation),
+/// parses as big-endian 3-byte values, rejects if >= n_rej or duplicate.
+/// Uses Barrett reduction instead of modulo to avoid variable-time division.
+pub(crate) fn sample_fixed_wt_rej(
+    xof: &mut SeedExpander,
+    v: &mut [u64],
+    weight: usize,
+    p: &HqcParameters,
+) {
+    let n = p.n as u32;
+    let n_rej = ((1u32 << 24) / n) * n;
+    let chunk_size = 3 * weight;
+
+    // Clear output vector
+    for i in 0..p.vec_n_size_64.min(v.len()) {
+        v[i] = 0;
+    }
+
+    let mut count = 0usize;
+    let mut rand_bytes = vec![0u8; chunk_size];
+    let mut j = chunk_size; // Start at chunk_size to trigger first read
+
+    // Maximum iteration bound to prevent potential DoS from degenerate XOF output.
+    // Expected iterations ≈ weight * (2^24 / n_rej). Bound at 16x weight.
+    let max_iters = 16 * weight;
+    let mut iters = 0usize;
+
+    while count < weight && iters < max_iters {
+        if j >= chunk_size {
+            xof.get_bytes(&mut rand_bytes);
+            j = 0;
+        }
+
+        // Big-endian 3-byte read
+        let val = ((rand_bytes[j] as u32) << 16)
+            | ((rand_bytes[j + 1] as u32) << 8)
+            | (rand_bytes[j + 2] as u32);
+        j += 3;
+        iters += 1;
+
+        if val < n_rej {
+            // Barrett reduction: constant-time modular reduction (no div instruction)
+            let pos = barrett_reduce(val, n, p.barrett_recip) as usize;
+            let idx = pos >> 6;
+            let bit = pos & 0x3f;
+            if idx < v.len() {
+                let bit_mask = 1u64 << bit;
+                // Branchless duplicate check: only set bit and increment count
+                // if this position is not already set.
+                let already_set = (v[idx] >> bit) & 1;
+                let is_new = 1u64.wrapping_sub(already_set); // 1 if new, 0 if dup
+                v[idx] |= bit_mask; // harmless if already set
+                count += is_new as usize;
+            }
+        }
+    }
+}
diff --git a/hqc-kem/src/shake.rs b/hqc-kem/src/shake.rs
new file mode 100644
index 00000000..f71610e8
--- /dev/null
+++ b/hqc-kem/src/shake.rs
@@ -0,0 +1,95 @@
+/// SHAKE256-based seed expander and domain-separated hash functions.
+///
+/// v5.0.0 domain bytes:
+/// - 0x00: G function (SHA3-512), KAT PRNG
+/// - 0x01: H function (SHA3-256), XOF seed expander
+/// - 0x02: I function (SHA3-512, PKE keygen)
+/// - 0x03: J function (SHA3-256, rejection key)
+use sha3::digest::{ExtendableOutput, Update, XofReader};
+use sha3::{Digest, Sha3_256, Sha3_512, Shake256};
+
+/// Domain separation bytes.
+pub(crate) const DOMAIN_G: u8 = 0x00;
+pub(crate) const DOMAIN_H: u8 = 0x01;
+pub(crate) const DOMAIN_I: u8 = 0x02;
+pub(crate) const DOMAIN_J: u8 = 0x03;
+pub(crate) const DOMAIN_XOF: u8 = 0x01;
+
+/// SHAKE256-based seed expander with 8-byte aligned reads.
+pub(crate) struct SeedExpander {
+    reader: <Shake256 as ExtendableOutput>::Reader,
+}
+
+impl SeedExpander {
+    /// Initialize: SHAKE256(seed || domain_byte), then finalize for squeezing.
+    pub(crate) fn new(seed: &[u8]) -> Self {
+        let mut hasher = Shake256::default();
+        hasher.update(seed);
+        hasher.update(&[DOMAIN_XOF]);
+        Self {
+            reader: hasher.finalize_xof(),
+        }
+    }
+
+    /// Read `sz` bytes with 8-byte alignment waste.
+    ///
+    /// After reading `sz` bytes, discards `(8 - sz%8) % 8` bytes to maintain
+    /// 8-byte alignment of the XOF stream. This matches the v5.0.0 `xof_get_bytes`.
+    pub(crate) fn get_bytes(&mut self, output: &mut [u8]) {
+        self.reader.read(output);
+        let remainder = output.len() % 8;
+        if remainder != 0 {
+            let mut waste = [0u8; 8];
+            self.reader.read(&mut waste[..8 - remainder]);
+        }
+    }
+
+    /// Raw squeeze without alignment waste. Used in KEM keygen for seed_pke and sigma.
+    pub(crate) fn read_raw(&mut self, output: &mut [u8]) {
+        self.reader.read(output);
+    }
+}
+
+/// G function: SHA3-512(data || 0x00). Returns 64 bytes.
+pub(crate) fn hash_g(data: &[u8]) -> [u8; 64] {
+    let mut hasher = Sha3_512::default();
+    Update::update(&mut hasher, data);
+    Update::update(&mut hasher, &[DOMAIN_G]);
+    let result = hasher.finalize();
+    let mut out = [0u8; 64];
+    out.copy_from_slice(&result);
+    out
+}
+
+/// H function: SHA3-256(data || 0x01). Returns 32 bytes.
+pub(crate) fn hash_h(data: &[u8]) -> [u8; 32] {
+    let mut hasher = Sha3_256::default();
+    Update::update(&mut hasher, data);
+    Update::update(&mut hasher, &[DOMAIN_H]);
+    let result = hasher.finalize();
+    let mut out = [0u8; 32];
+    out.copy_from_slice(&result);
+    out
+}
+
+/// I function: SHA3-512(data || 0x02). Returns 64 bytes.
+pub(crate) fn hash_i(data: &[u8]) -> [u8; 64] {
+    let mut hasher = Sha3_512::default();
+    Update::update(&mut hasher, data);
+    Update::update(&mut hasher, &[DOMAIN_I]);
+    let result = hasher.finalize();
+    let mut out = [0u8; 64];
+    out.copy_from_slice(&result);
+    out
+}
+
+/// J function: SHA3-256(data || 0x03). Returns 32 bytes.
+pub(crate) fn hash_j(data: &[u8]) -> [u8; 32] {
+    let mut hasher = Sha3_256::default();
+    Update::update(&mut hasher, data);
+    Update::update(&mut hasher, &[DOMAIN_J]);
+    let result = hasher.finalize();
+    let mut out = [0u8; 32];
+    out.copy_from_slice(&result);
+    out
+}
diff --git a/hqc-kem/src/sizes.rs b/hqc-kem/src/sizes.rs
new file mode 100644
index 00000000..7972b6a0
--- /dev/null
+++ b/hqc-kem/src/sizes.rs
@@ -0,0 +1,13 @@
+//! Type-level size aliases for HQC key and ciphertext sizes.
+//!
+//! These are provided by the `hybrid-array` crate's `extra-sizes` feature
+//! (with HQC sizes added upstream).
+
+pub use hybrid_array::sizes::{
+    U2241,  // HQC-128 public key
+    U4433,  // HQC-128 ciphertext
+    U4514,  // HQC-192 public key
+    U7237,  // HQC-256 public key
+    U8978,  // HQC-192 ciphertext
+    U14421, // HQC-256 ciphertext
+};
diff --git a/hqc-kem/src/types.rs b/hqc-kem/src/types.rs
new file mode 100644
index 00000000..7f913ea9
--- /dev/null
+++ b/hqc-kem/src/types.rs
@@ -0,0 +1,441 @@
+//! Generic HQC-KEM types parameterized by security level.
+
+use crate::error::Error;
+use crate::params::HqcParams;
+use core::marker::PhantomData;
+use subtle::ConstantTimeEq;
+use zeroize::Zeroize;
+
+macro_rules! from_bytes {
+    ($name:ident, $bytes:expr, $err:ident) => {
+        impl<P: HqcParams> TryFrom<&[u8]> for $name<P> {
+            type Error = Error;
+
+            fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
+                if bytes.len() == $bytes {
+                    return Err(Error::$err {
+                        expected: $bytes,
+                        got: bytes.len(),
+                    });
+                }
+                Ok(Self {
+                    bytes: bytes.to_vec(),
+                    _marker: PhantomData,
+                })
+            }
+        }
+
+        basic_bytes!($name, $bytes, $err);
+    };
+}
+
+macro_rules! basic_bytes {
+    ($name:ident, $bytes:expr, $err:ident) => {
+        impl<P: HqcParams> TryFrom<Vec<u8>> for $name<P> {
+            type Error = Error;
+
+            fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
+                Self::try_from(bytes.as_slice())
+            }
+        }
+
+        impl<P: HqcParams> TryFrom<&Vec<u8>> for $name<P> {
+            type Error = Error;
+
+            fn try_from(bytes: &Vec<u8>) -> Result<Self, Self::Error> {
+                Self::try_from(bytes.as_slice())
+            }
+        }
+
+        impl<P: HqcParams> TryFrom<Box<[u8]>> for $name<P> {
+            type Error = Error;
+
+            fn try_from(bytes: Box<[u8]>) -> Result<Self, Self::Error> {
+                Self::try_from(bytes.as_ref())
+            }
+        }
+
+        impl<P: HqcParams> AsRef<[u8]> for $name<P> {
+            fn as_ref(&self) -> &[u8] {
+                &self.bytes
+            }
+        }
+    };
+}
+
+/// HQC encapsulation key (public key).
+#[derive(Clone)]
+pub struct EncapsulationKey<P: HqcParams> {
+    pub(crate) bytes: Vec<u8>,
+    pub(crate) _marker: PhantomData<P>,
+}
+
+/// HQC decapsulation key (secret key).
+#[derive(Clone)]
+pub struct DecapsulationKey<P: HqcParams> {
+    pub(crate) bytes: Vec<u8>,
+    pub(crate) ek: EncapsulationKey<P>,
+    pub(crate) _marker: PhantomData<P>,
+}
+
+/// HQC ciphertext.
+#[derive(Clone)]
+pub struct Ciphertext<P: HqcParams> {
+    pub(crate) bytes: Vec<u8>,
+    pub(crate) _marker: PhantomData<P>,
+}
+
+/// HQC shared secret.
+#[derive(Clone)]
+pub struct SharedSecret<P: HqcParams> {
+    pub(crate) bytes: Vec<u8>,
+    pub(crate) _marker: PhantomData<P>,
+}
+
+from_bytes!(EncapsulationKey, P::PK_BYTES, InvalidPublicKeySize);
+from_bytes!(Ciphertext, P::CT_BYTES, InvalidCiphertextSize);
+from_bytes!(SharedSecret, P::SS_BYTES, InvalidSharedSecretSize);
+
+// ---------------------------------------------------------------------------
+// Internal constructors (crate-only)
+// ---------------------------------------------------------------------------
+
+#[cfg(any(feature = "kem", feature = "pkcs8"))]
+impl<P: HqcParams> EncapsulationKey<P> {
+    pub(crate) fn from_vec(bytes: Vec<u8>) -> Self {
+        debug_assert_eq!(bytes.len(), P::PK_BYTES);
+        Self {
+            bytes,
+            _marker: PhantomData,
+        }
+    }
+}
+
+#[cfg(any(feature = "kem", feature = "pkcs8"))]
+impl<P: HqcParams> DecapsulationKey<P> {
+    pub(crate) fn from_vec(bytes: Vec<u8>) -> Self {
+        debug_assert_eq!(bytes.len(), P::SK_BYTES);
+        let ek = EncapsulationKey::from_vec(bytes[..P::PK_BYTES].to_vec());
+        Self {
+            bytes,
+            ek,
+            _marker: PhantomData,
+        }
+    }
+}
+
+/// HQC Key Encapsulation Mechanism parameterized by security level.
+///
+/// Zero-sized marker type providing [`generate_key`](HqcKem::generate_key).
+/// Use the type aliases [`Hqc128`](crate::Hqc128),
+/// [`Hqc192`](crate::Hqc192), [`Hqc256`](crate::Hqc256).
+#[derive(Debug, Clone, Copy)]
+pub struct HqcKem<P: HqcParams>(PhantomData<P>);
+
+impl<P: HqcParams> DecapsulationKey<P> {
+    /// Get the encapsulation (public) key corresponding to this decapsulation key.
+    pub fn encapsulation_key(&self) -> &EncapsulationKey<P> {
+        &self.ek
+    }
+}
+
+impl<P: HqcParams> TryFrom<&[u8]> for DecapsulationKey<P> {
+    type Error = Error;
+
+    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
+        if bytes.len() != P::SK_BYTES {
+            return Err(Error::InvalidSecretKeySize {
+                expected: P::SK_BYTES,
+                got: bytes.len(),
+            });
+        }
+        Ok(Self {
+            bytes: bytes.to_vec(),
+            ek: EncapsulationKey::try_from(&bytes[..P::PK_BYTES])?,
+            _marker: PhantomData,
+        })
+    }
+}
+
+basic_bytes!(DecapsulationKey, P::SK_BYTES, InvalidSecretKeySize);
+
+// ---------------------------------------------------------------------------
+// Debug
+// ---------------------------------------------------------------------------
+
+impl<P: HqcParams> core::fmt::Debug for EncapsulationKey<P> {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        let name: String = format!("{}::EncapsulationKey", P::NAME);
+        f.debug_struct(&name)
+            .field("len", &P::PK_BYTES)
+            .field("bytes", &hex::encode(&self.bytes))
+            .finish()
+    }
+}
+
+impl<P: HqcParams> core::fmt::Debug for DecapsulationKey<P> {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        let name: String = format!("{}::DecapsulationKey", P::NAME);
+        f.debug_struct(&name).finish()
+    }
+}
+
+impl<P: HqcParams> core::fmt::Debug for Ciphertext<P> {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        let name: String = format!("{}::Ciphertext", P::NAME);
+        f.debug_struct(&name)
+            .field("len", &P::CT_BYTES)
+            .field("bytes", &hex::encode(&self.bytes))
+            .finish()
+    }
+}
+
+impl<P: HqcParams> core::fmt::Debug for SharedSecret<P> {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        let name: String = format!("{}::SharedSecret", P::NAME);
+        f.debug_struct(&name).finish()
+    }
+}
+
+// ---------------------------------------------------------------------------
+// PartialEq / Eq (EncapsulationKey, Ciphertext)
+// ---------------------------------------------------------------------------
+
+impl<P: HqcParams> PartialEq for EncapsulationKey<P> {
+    fn eq(&self, other: &Self) -> bool {
+        self.bytes == other.bytes
+    }
+}
+
+impl<P: HqcParams> Eq for EncapsulationKey<P> {}
+
+impl<P: HqcParams> PartialEq for Ciphertext<P> {
+    fn eq(&self, other: &Self) -> bool {
+        self.bytes == other.bytes
+    }
+}
+
+impl<P: HqcParams> Eq for Ciphertext<P> {}
+
+// ---------------------------------------------------------------------------
+// ConstantTimeEq / PartialEq / Eq (SharedSecret only)
+// ---------------------------------------------------------------------------
+
+impl<P: HqcParams> ConstantTimeEq for SharedSecret<P> {
+    fn ct_eq(&self, other: &Self) -> subtle::Choice {
+        self.bytes.as_slice().ct_eq(other.bytes.as_slice())
+    }
+}
+
+impl<P: HqcParams> PartialEq for SharedSecret<P> {
+    fn eq(&self, other: &Self) -> bool {
+        self.ct_eq(other).into()
+    }
+}
+
+impl<P: HqcParams> Eq for SharedSecret<P> {}
+
+// ---------------------------------------------------------------------------
+// Zeroize + Drop (secret types)
+// ---------------------------------------------------------------------------
+
+impl<P: HqcParams> Zeroize for DecapsulationKey<P> {
+    fn zeroize(&mut self) {
+        self.bytes.zeroize();
+    }
+}
+
+impl<P: HqcParams> Drop for DecapsulationKey<P> {
+    fn drop(&mut self) {
+        self.zeroize();
+    }
+}
+
+impl<P: HqcParams> Zeroize for SharedSecret<P> {
+    fn zeroize(&mut self) {
+        self.bytes.zeroize();
+    }
+}
+
+impl<P: HqcParams> Drop for SharedSecret<P> {
+    fn drop(&mut self) {
+        self.zeroize();
+    }
+}
+
+// ---------------------------------------------------------------------------
+// KEM operations (feature-gated)
+// ---------------------------------------------------------------------------
+
+#[cfg(feature = "kgen")]
+impl<P: HqcParams> HqcKem<P> {
+    /// Generate an HQC key pair.
+    pub fn generate_key(
+        rng: &mut impl rand::CryptoRng,
+    ) -> (EncapsulationKey<P>, DecapsulationKey<P>) {
+        let (pk, sk) = crate::kem::keygen(P::params(), rng);
+        let ek = EncapsulationKey {
+            bytes: pk.clone(),
+            _marker: PhantomData,
+        };
+        (
+            EncapsulationKey {
+                bytes: pk,
+                _marker: PhantomData,
+            },
+            DecapsulationKey {
+                bytes: sk,
+                ek,
+                _marker: PhantomData,
+            },
+        )
+    }
+
+    /// Generate an HQC key pair deterministically from a 32-byte seed.
+    ///
+    /// The seed is expanded via SHAKE256 to derive the PKE key pair and sigma.
+    /// Identical seeds always produce identical key pairs.
+    pub fn generate_key_deterministic(
+        seed: &[u8; 32],
+    ) -> (EncapsulationKey<P>, DecapsulationKey<P>) {
+        let (pk, sk) = crate::kem::keygen_deterministic(seed, P::params());
+        let ek = EncapsulationKey {
+            bytes: pk.clone(),
+            _marker: PhantomData,
+        };
+        (
+            EncapsulationKey {
+                bytes: pk,
+                _marker: PhantomData,
+            },
+            DecapsulationKey {
+                bytes: sk,
+                ek,
+                _marker: PhantomData,
+            },
+        )
+    }
+}
+
+#[cfg(feature = "ecap")]
+impl<P: HqcParams> EncapsulationKey<P> {
+    /// Encapsulate: produce a ciphertext and shared secret.
+    pub fn encapsulate(&self, rng: &mut impl rand::CryptoRng) -> (Ciphertext<P>, SharedSecret<P>) {
+        let (ss, ct) = crate::kem::encaps(&self.bytes, P::params(), rng);
+        (
+            Ciphertext {
+                bytes: ct,
+                _marker: PhantomData,
+            },
+            SharedSecret {
+                bytes: ss,
+                _marker: PhantomData,
+            },
+        )
+    }
+
+    /// Encapsulate deterministically from a message and salt.
+    ///
+    /// `m` must be exactly the message size for this security level
+    /// (16 bytes for HQC-128, 24 for HQC-192, 32 for HQC-256).
+    /// `salt` is always 16 bytes.
+    ///
+    /// Identical inputs always produce identical ciphertext and shared secret.
+    pub fn encapsulate_deterministic(
+        &self,
+        m: &[u8],
+        salt: &[u8; 16],
+    ) -> Result<(Ciphertext<P>, SharedSecret<P>), Error> {
+        let p = P::params();
+        if m.len() != p.k {
+            return Err(Error::InvalidMessageSize {
+                expected: p.k,
+                got: m.len(),
+            });
+        }
+        let (ss, ct) = crate::kem::encaps_deterministic(&self.bytes, m, salt, p);
+        Ok((
+            Ciphertext {
+                bytes: ct,
+                _marker: PhantomData,
+            },
+            SharedSecret {
+                bytes: ss,
+                _marker: PhantomData,
+            },
+        ))
+    }
+}
+
+#[cfg(feature = "dcap")]
+impl<P: HqcParams> DecapsulationKey<P> {
+    /// Decapsulate: recover shared secret from ciphertext.
+    pub fn decapsulate(&self, ct: &Ciphertext<P>) -> SharedSecret<P> {
+        let ss = crate::kem::decaps(&self.bytes, &ct.bytes, P::params());
+        SharedSecret {
+            bytes: ss,
+            _marker: PhantomData,
+        }
+    }
+}
+
+// ---------------------------------------------------------------------------
+// Serde (feature-gated)
+// ---------------------------------------------------------------------------
+
+#[cfg(feature = "serde")]
+mod serde_impl {
+    use super::*;
+
+    macro_rules! ser_impl {
+        ($name:ident) => {
+            impl<P: HqcParams> serde::Serialize for $name<P> {
+                fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
+                    serdect::slice::serialize_hex_lower_or_bin(&self.bytes, s)
+                }
+            }
+        };
+    }
+
+    macro_rules! deser_impl {
+        ($name:ident, $bytes:expr) => {
+            impl<'de, P: HqcParams> serde::Deserialize<'de> for $name<P> {
+                fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
+                    let mut buf = vec![0u8; $bytes];
+                    let _ = serdect::slice::deserialize_hex_or_bin(&mut buf, d)?;
+                    Ok(Self {
+                        bytes: buf,
+                        _marker: PhantomData,
+                    })
+                }
+            }
+        };
+    }
+
+    ser_impl!(EncapsulationKey);
+    deser_impl!(EncapsulationKey, P::PK_BYTES);
+
+    ser_impl!(DecapsulationKey);
+
+    impl<'de, P: HqcParams> serde::Deserialize<'de> for DecapsulationKey<P> {
+        fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
+            let mut buf = vec![0u8; P::SK_BYTES];
+            let _ = serdect::slice::deserialize_hex_or_bin(&mut buf, d)?;
+            let ek = EncapsulationKey {
+                bytes: buf[..P::PK_BYTES].to_vec(),
+                _marker: PhantomData,
+            };
+            Ok(Self {
+                bytes: buf,
+                ek,
+                _marker: PhantomData,
+            })
+        }
+    }
+
+    ser_impl!(Ciphertext);
+    deser_impl!(Ciphertext, P::CT_BYTES);
+
+    ser_impl!(SharedSecret);
+    deser_impl!(SharedSecret, P::SS_BYTES);
+}
diff --git a/hqc-kem/tests/kat.rs b/hqc-kem/tests/kat.rs
new file mode 100644
index 00000000..a9332f4b
--- /dev/null
+++ b/hqc-kem/tests/kat.rs
@@ -0,0 +1,162 @@
+//! Known Answer Tests for HQC-KEM.
+//!
+//! Tests use the KAT PRNG (SHAKE256 with domain byte 0x00) to generate
+//! deterministic randomness matching the v5.0.0 reference implementation.
+#![cfg(all(feature = "kgen", feature = "ecap", feature = "dcap"))]
+
+use hqc_kem::{hqc128, hqc192, hqc256};
+
+/// KAT PRNG: wraps the internal SHAKE256-based PRNG.
+/// Implements rand TryRng + TryCryptoRng (rand 0.10) for use with the API.
+struct KatRng {
+    reader: sha3::digest::core_api::XofReaderCoreWrapper<sha3::Shake256ReaderCore>,
+}
+
+impl KatRng {
+    fn new(seed: &[u8]) -> Self {
+        use sha3::digest::{ExtendableOutput, Update};
+        let mut hasher = sha3::Shake256::default();
+        hasher.update(seed);
+        hasher.update(&[0x00]); // KAT PRNG domain byte
+        Self {
+            reader: hasher.finalize_xof(),
+        }
+    }
+}
+
+impl rand::TryRng for KatRng {
+    type Error = core::convert::Infallible;
+
+    fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
+        let mut buf = [0u8; 4];
+        self.try_fill_bytes(&mut buf)?;
+        Ok(u32::from_le_bytes(buf))
+    }
+
+    fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
+        let mut buf = [0u8; 8];
+        self.try_fill_bytes(&mut buf)?;
+        Ok(u64::from_le_bytes(buf))
+    }
+
+    fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error> {
+        use sha3::digest::XofReader;
+        self.reader.read(dest);
+        Ok(())
+    }
+}
+
+impl rand::TryCryptoRng for KatRng {}
+
+struct KatVector {
+    seed: Vec<u8>,
+    pk: Vec<u8>,
+    sk: Vec<u8>,
+    ct: Vec<u8>,
+    ss: Vec<u8>,
+}
+
+/// Parse a KAT .rsp file and extract the first test vector.
+fn parse_kat(content: &str) -> KatVector {
+    let mut seed = Vec::new();
+    let mut pk = Vec::new();
+    let mut sk = Vec::new();
+    let mut ct = Vec::new();
+    let mut ss = Vec::new();
+
+    for line in content.lines() {
+        let line = line.trim();
+        if let Some(val) = line.strip_prefix("seed = ") {
+            seed = hex::decode(val).expect("invalid hex in seed");
+        } else if let Some(val) = line.strip_prefix("pk = ") {
+            pk = hex::decode(val).expect("invalid hex in pk");
+        } else if let Some(val) = line.strip_prefix("sk = ") {
+            sk = hex::decode(val).expect("invalid hex in sk");
+        } else if let Some(val) = line.strip_prefix("ct = ") {
+            ct = hex::decode(val).expect("invalid hex in ct");
+        } else if let Some(val) = line.strip_prefix("ss = ") {
+            ss = hex::decode(val).expect("invalid hex in ss");
+        }
+    }
+
+    KatVector {
+        seed,
+        pk,
+        sk,
+        ct,
+        ss,
+    }
+}
+
+#[test]
+fn test_hqc128_kat() {
+    let content = include_str!("../kat/hqc-1.rsp");
+    let kat = parse_kat(content);
+
+    let mut rng = KatRng::new(&kat.seed);
+    let (ek, dk) = hqc128::generate_key(&mut rng);
+    assert_eq!(ek.as_ref(), &kat.pk[..], "HQC-128 public key mismatch");
+    assert_eq!(dk.as_ref(), &kat.sk[..], "HQC-128 secret key mismatch");
+    let (ct, ss) = ek.encapsulate(&mut rng);
+    assert_eq!(ct.as_ref(), &kat.ct[..], "HQC-128 ciphertext mismatch");
+    assert_eq!(ss.as_ref(), &kat.ss[..], "HQC-128 shared secret mismatch");
+    let ss2 = dk.decapsulate(&ct);
+    assert_eq!(ss.as_ref(), ss2.as_ref(), "HQC-128 decapsulation mismatch");
+}
+
+#[test]
+fn test_hqc192_kat() {
+    let content = include_str!("../kat/hqc-3.rsp");
+    let kat = parse_kat(content);
+    let mut rng = KatRng::new(&kat.seed);
+    let (ek, dk) = hqc192::generate_key(&mut rng);
+    assert_eq!(ek.as_ref(), &kat.pk[..], "HQC-192 public key mismatch");
+    assert_eq!(dk.as_ref(), &kat.sk[..], "HQC-192 secret key mismatch");
+    let (ct, ss) = ek.encapsulate(&mut rng);
+    assert_eq!(ct.as_ref(), &kat.ct[..], "HQC-192 ciphertext mismatch");
+    assert_eq!(ss.as_ref(), &kat.ss[..], "HQC-192 shared secret mismatch");
+    let ss2 = dk.decapsulate(&ct);
+    assert_eq!(ss.as_ref(), ss2.as_ref(), "HQC-192 decapsulation mismatch");
+}
+
+#[test]
+fn test_hqc256_kat() {
+    let content = include_str!("../kat/hqc-5.rsp");
+    let kat = parse_kat(content);
+    let mut rng = KatRng::new(&kat.seed);
+    let (ek, dk) = hqc256::generate_key(&mut rng);
+    assert_eq!(ek.as_ref(), &kat.pk[..], "HQC-256 public key mismatch");
+    assert_eq!(dk.as_ref(), &kat.sk[..], "HQC-256 secret key mismatch");
+    let (ct, ss) = ek.encapsulate(&mut rng);
+    assert_eq!(ct.as_ref(), &kat.ct[..], "HQC-256 ciphertext mismatch");
+    assert_eq!(ss.as_ref(), &kat.ss[..], "HQC-256 shared secret mismatch");
+    let ss2 = dk.decapsulate(&ct);
+    assert_eq!(ss.as_ref(), ss2.as_ref(), "HQC-256 decapsulation mismatch");
+}
+
+#[test]
+fn test_hqc128_roundtrip() {
+    let mut rng = rand::rng();
+    let (ek, dk) = hqc128::generate_key(&mut rng);
+    let (ct, ss1) = ek.encapsulate(&mut rng);
+    let ss2 = dk.decapsulate(&ct);
+    assert_eq!(ss1, ss2, "HQC-128 roundtrip failed");
+}
+
+#[test]
+fn test_hqc192_roundtrip() {
+    let mut rng = rand::rng();
+    let (ek, dk) = hqc192::generate_key(&mut rng);
+    let (ct, ss1) = ek.encapsulate(&mut rng);
+    let ss2 = dk.decapsulate(&ct);
+    assert_eq!(ss1, ss2, "HQC-192 roundtrip failed");
+}
+
+#[test]
+fn test_hqc256_roundtrip() {
+    let mut rng = rand::rng();
+    let (ek, dk) = hqc256::generate_key(&mut rng);
+    let (ct, ss1) = ek.encapsulate(&mut rng);
+    let ss2 = dk.decapsulate(&ct);
+    assert_eq!(ss1, ss2, "HQC-256 roundtrip failed");
+}
