diff --git a/.github/workflows/hqc-kem.yml b/.github/workflows/hqc-kem.yml new file mode 100644 index 0000000..9a19955 --- /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 d1a8519..9fc8ccf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,15 +30,15 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anyhow" -version = "1.0.100" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "atomic-polyfill" @@ -63,15 +63,24 @@ checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6" [[package]] name = "base64ct" -version = "1.8.2" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d809780667f4410e7c41b07f52439b94d2bdf8528eeedc287fa38d3b7f95d82" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] +name = "block-buffer" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] [[package]] name = "block-buffer" @@ -84,9 +93,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.1" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "byteorder" @@ -102,9 +111,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.52" +version = "1.2.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" +checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" dependencies = [ "find-msvc-tools", "shlex", @@ -118,13 +127,13 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chacha20" -version = "0.10.0-rc.10" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c536927023d1c432e6e23a25ef45f6756094eac2ab460db5fb17a772acdfd312" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" dependencies = [ "cfg-if", "cipher", - "cpufeatures 0.2.17", + "cpufeatures 0.3.0", "rand_core", ] @@ -161,25 +170,25 @@ 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", ] [[package]] name = "clap" -version = "4.5.54" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.54" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstyle", "clap_lex", @@ -187,9 +196,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.6" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "cmov" @@ -308,9 +317,9 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-bigint" -version = "0.7.0-rc.25" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cba9eeeb213f7fd29353032f71f7c173e5f6d95d85151cb3a47197b0ea7e8be7" +checksum = "42a0d26b245348befa0c121944541476763dcc46ede886c88f9d12e1697d27c3" dependencies = [ "cpubits", "ctutils", @@ -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.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c" dependencies = [ - "block-buffer", + "block-buffer 0.12.0", "const-oid", - "crypto-common", + "crypto-common 0.2.1", "ctutils", ] @@ -418,14 +447,14 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "elliptic-curve" -version = "0.14.0-rc.28" +version = "0.14.0-rc.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bde7860544606d222fd6bd6d9f9a0773321bf78072a637e1d560a058c0031978" +checksum = "e84043d573efd4ac9d2d125817979a379204bf7e328b25a4a30487e8d100e618" dependencies = [ "base16ct", "crypto-bigint", - "crypto-common", - "digest", + "crypto-common 0.2.1", + "digest 0.11.2", "hkdf", "hybrid-array", "rand_core", @@ -462,9 +491,9 @@ checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" [[package]] name = "find-msvc-tools" -version = "0.1.7" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "foldhash" @@ -492,7 +521,7 @@ dependencies = [ "serde_json", "serde_yaml", "serdect", - "sha3", + "sha3 0.11.0-rc.9", "subtle", "thiserror", "toml", @@ -501,15 +530,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", @@ -518,9 +547,9 @@ dependencies = [ [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-timer" @@ -530,23 +559,32 @@ checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-core", "futures-macro", "futures-task", "pin-project-lite", - "pin-utils", "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.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", "libc", @@ -640,27 +678,47 @@ checksum = "e712f64ec3850b98572bffac52e2c6f282b29fe6c5fa6d42334b30be438d95c1" [[package]] name = "hkdf" -version = "0.13.0-rc.5" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbb55385998ae66b8d2d5143c05c94b9025ab863966f0c94ce7a5fde30105092" +checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018" dependencies = [ "hmac", ] [[package]] name = "hmac" -version = "0.13.0-rc.6" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60017b071c523c9e5a55dd1253582bff6150c5e96a7e8511e419de1ab5ee97f9" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" +dependencies = [ + "digest 0.11.2", +] + +[[package]] +name = "hqc-kem" +version = "0.1.0" dependencies = [ - "digest", + "const-oid", + "criterion", + "hex", + "hybrid-array", + "kem", + "pkcs8", + "rand", + "serde", + "serdect", + "sha3 0.10.8", + "subtle", + "thiserror", + "typenum", + "zeroize", ] [[package]] name = "hybrid-array" -version = "0.4.8" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8655f91cd07f2b9d0c24137bd650fe69617773435ee5ec83022377777ce65ef1" +checksum = "3944cf8cf766b40e2a1a333ee5e9b563f854d5fa49d6a8ca2764e97c6eddb214" dependencies = [ "ctutils", "subtle", @@ -706,15 +764,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "js-sys" -version = "0.3.83" +version = "0.3.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +checksum = "797146bb2677299a1eb6b7b50a890f4c361b29ef967addf5b2fa45dae1bb6d7d" dependencies = [ "once_cell", "wasm-bindgen", @@ -722,14 +780,23 @@ dependencies = [ [[package]] name = "k256" -version = "0.14.0-rc.7" +version = "0.14.0-rc.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83da23da11f0b5db6f23d9280a84b3a33a746aa43ebb9270d6b445991da9cee3" +checksum = "f7d2c6c227649d5ec80eaae541f1736232641a0bcdb3062a52b34edb42054158" dependencies = [ "cpubits", "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" @@ -746,7 +813,7 @@ version = "0.3.0-rc.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3ae2c3347ff4a7af4f679a9e397c2c7e6034a00b773dd2dd3c001d7f40897c9" dependencies = [ - "crypto-common", + "crypto-common 0.2.1", "rand_core", ] @@ -758,9 +825,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.180" +version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "lock_api" @@ -779,9 +846,9 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "ml-kem" @@ -800,7 +867,7 @@ dependencies = [ "rand_core", "serde", "serde_json", - "sha3", + "sha3 0.11.0-rc.9", "zeroize", ] @@ -856,9 +923,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "oorandom" @@ -868,9 +935,9 @@ checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "openssl-sys" -version = "0.9.111" +version = "0.9.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" dependencies = [ "cc", "libc", @@ -880,9 +947,9 @@ dependencies = [ [[package]] name = "p256" -version = "0.14.0-rc.7" +version = "0.14.0-rc.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "018bfbb86e05fd70a83e985921241035ee09fcd369c4a2c3680b389a01d2ad28" +checksum = "44f0a10fe314869359cb2901342b045f4e5a962ef9febc006f03d2a8c848fe4c" dependencies = [ "elliptic-curve", "primefield", @@ -891,9 +958,9 @@ dependencies = [ [[package]] name = "p384" -version = "0.14.0-rc.7" +version = "0.14.0-rc.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c91df688211f5957dbe2ab599dcbcaade8d6d3cdc15c5b350d350d7d07ce423" +checksum = "b079e66810c55ab3d6ba424e056dc4aefcdb8046c8c3f3816142edbdd7af7721" dependencies = [ "elliptic-curve", "fiat-crypto", @@ -903,9 +970,9 @@ dependencies = [ [[package]] name = "p521" -version = "0.14.0-rc.7" +version = "0.14.0-rc.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de6cd9451de522549d36cc78a1b45a699a3d55a872e8ea0c8f0318e502d99e2c" +checksum = "9eecc34c4c6e6596d5271fecf90ac4f16593fa198e77282214d0c22736aa9266" dependencies = [ "base16ct", "elliptic-curve", @@ -924,15 +991,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pkcs8" @@ -1003,12 +1064,12 @@ dependencies = [ [[package]] name = "primefield" -version = "0.14.0-rc.7" +version = "0.14.0-rc.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93401c13cc7ff24684571cfca9d3cf9ebabfaf3d4b7b9963ade41ec54da196b5" +checksum = "c6543f5eec854fbf74ba5ef651fbdc9408919b47c3e1526623687135c16d12e9" dependencies = [ "crypto-bigint", - "crypto-common", + "crypto-common 0.2.1", "rand_core", "rustcrypto-ff", "subtle", @@ -1017,45 +1078,56 @@ dependencies = [ [[package]] name = "primeorder" -version = "0.14.0-rc.7" +version = "0.14.0-rc.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c5c8a39bcd764bfedf456e8d55e115fe86dda3e0f555371849f2a41cbc9706" +checksum = "569d9ad6ef822bb0322c7e7d84e5e286244050bd5246cac4c013535ae91c2c90" dependencies = [ "elliptic-curve", ] [[package]] name = "proc-macro-crate" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ "toml_edit", ] [[package]] name = "proc-macro2" -version = "1.0.105" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.43" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" -version = "5.3.0" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" +dependencies = [ + "chacha20", + "getrandom", + "rand_core", +] [[package]] name = "rand_core" @@ -1085,9 +1157,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -1097,9 +1169,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -1108,9 +1180,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.8" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "relative-path" @@ -1158,9 +1230,9 @@ dependencies = [ [[package]] name = "rustcrypto-ff" -version = "0.14.0-rc.0" +version = "0.14.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5db129183b2c139d7d87d08be57cba626c715789db17aec65c8866bfd767d1f" +checksum = "fd2a8adb347447693cd2ba0d218c4b66c62da9b0a5672b17b981e4291ec65ff6" dependencies = [ "rand_core", "subtle", @@ -1185,9 +1257,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "same-file" @@ -1206,9 +1278,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sec1" -version = "0.8.0-rc.13" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2400ed44a13193820aa528a19f376c3843141a8ce96ff34b11104cc79763f2" +checksum = "f46b9a5ab87780a3189a1d704766579517a04ad59de653b7aad7d38e8a15f7dc" dependencies = [ "base16ct", "ctutils", @@ -1288,9 +1360,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +checksum = "876ac351060d4f882bb1032b6369eb0aef79ad9df1ea8bc404874d8cc3d0cd98" dependencies = [ "serde_core", ] @@ -1326,7 +1398,17 @@ checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" dependencies = [ "cfg-if", "cpufeatures 0.3.0", - "digest", + "digest 0.11.2", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest 0.10.7", + "keccak 0.1.6", ] [[package]] @@ -1335,8 +1417,8 @@ version = "0.11.0-rc.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b233a7d59d7bfc027208506a33ffc9532b2acb24ddc61fe7e758dc2250db431" dependencies = [ - "digest", - "keccak", + "digest 0.11.2", + "keccak 0.2.0", ] [[package]] @@ -1347,9 +1429,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "slab" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "spin" @@ -1384,9 +1466,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.114" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -1395,18 +1477,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -1425,17 +1507,17 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.11+spec-1.1.0" +version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ "indexmap", "serde_core", "serde_spanned", - "toml_datetime", + "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", - "winnow", + "winnow 0.7.15", ] [[package]] @@ -1447,32 +1529,41 @@ dependencies = [ "serde_core", ] +[[package]] +name = "toml_datetime" +version = "1.1.0+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97251a7c317e03ad83774a8752a7e81fb6067740609f75ea2b585b569a59198f" +dependencies = [ + "serde_core", +] + [[package]] name = "toml_edit" -version = "0.23.10+spec-1.0.0" +version = "0.25.8+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +checksum = "16bff38f1d86c47f9ff0647e6838d7bb362522bdf44006c7068c2b1e606f1f3c" dependencies = [ "indexmap", - "toml_datetime", + "toml_datetime 1.1.0+spec-1.1.0", "toml_parser", - "winnow", + "winnow 1.0.1", ] [[package]] name = "toml_parser" -version = "1.0.6+spec-1.1.0" +version = "1.1.0+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011" dependencies = [ - "winnow", + "winnow 1.0.1", ] [[package]] name = "toml_writer" -version = "1.0.6+spec-1.1.0" +version = "1.1.0+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" +checksum = "d282ade6016312faf3e41e57ebbba0c073e4056dab1232ab1cb624199648f8ed" [[package]] name = "typenum" @@ -1482,9 +1573,9 @@ checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-xid" @@ -1504,6 +1595,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" @@ -1516,11 +1613,11 @@ dependencies = [ [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ - "wit-bindgen 0.46.0", + "wit-bindgen", ] [[package]] @@ -1529,14 +1626,14 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "wit-bindgen 0.51.0", + "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.106" +version = "0.2.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +checksum = "7dc0882f7b5bb01ae8c5215a1230832694481c1a4be062fd410e12ea3da5b631" dependencies = [ "cfg-if", "once_cell", @@ -1547,9 +1644,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.106" +version = "0.2.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +checksum = "75973d3066e01d035dbedaad2864c398df42f8dd7b1ea057c35b8407c015b537" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1557,9 +1654,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.106" +version = "0.2.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +checksum = "91af5e4be765819e0bcfee7322c14374dc821e35e72fa663a830bbc7dc199eac" dependencies = [ "bumpalo", "proc-macro2", @@ -1570,9 +1667,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.106" +version = "0.2.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +checksum = "c9bf0406a78f02f336bf1e451799cca198e8acde4ffa278f0fb20487b150a633" dependencies = [ "unicode-ident", ] @@ -1613,9 +1710,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.83" +version = "0.3.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +checksum = "749466a37ee189057f54748b200186b59a03417a117267baf3fd89cecc9fb837" dependencies = [ "js-sys", "wasm-bindgen", @@ -1647,18 +1744,18 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.14" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" -dependencies = [ - "memchr", -] +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" [[package]] -name = "wit-bindgen" -version = "0.46.0" +name = "winnow" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" +dependencies = [ + "memchr", +] [[package]] name = "wit-bindgen" @@ -1759,7 +1856,7 @@ dependencies = [ "rand_core", "serde", "serde_json", - "sha3", + "sha3 0.11.0-rc.9", "x25519-dalek", "zeroize", ] @@ -1777,18 +1874,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.33" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.33" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", @@ -1800,9 +1897,23 @@ 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" -version = "1.0.12" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index a5dedb5..bc13478 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.lock b/hqc-kem/Cargo.lock new file mode 100644 index 0000000..28bae06 --- /dev/null +++ b/hqc-kem/Cargo.lock @@ -0,0 +1,1139 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloca" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4" +dependencies = [ + "cc", +] + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base16ct" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[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 = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cc" +version = "1.2.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core", +] + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "criterion" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3" +dependencies = [ + "alloca", + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "itertools", + "num-traits", + "oorandom", + "page_size", + "plotters", + "rayon", + "regex", + "serde", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" +dependencies = [ + "hybrid-array", + "rand_core", +] + +[[package]] +name = "der" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fd89660b2dc699704064e59e9dba0147b903e85319429e131620d022be411b" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common 0.1.7", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "rand_core", + "wasip2", + "wasip3", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hqc-kem" +version = "0.1.0" +dependencies = [ + "const-oid", + "criterion", + "hex", + "hybrid-array", + "kem", + "pkcs8", + "rand", + "serde", + "serdect", + "sha3", + "subtle", + "thiserror", + "typenum", + "zeroize", +] + +[[package]] +name = "hybrid-array" +version = "0.4.9" +dependencies = [ + "typenum", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4c90f45aa2e6eacbe8645f77fdea542ac97a494bcd117a67df9ff4d611f995" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[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 = "kem" +version = "0.3.0-rc.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ae2c3347ff4a7af4f679a9e397c2c7e6034a00b773dd2dd3c001d7f40897c9" +dependencies = [ + "crypto-common 0.2.1", + "rand_core", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.183" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + +[[package]] +name = "page_size" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "pem-rfc7468" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9" +dependencies = [ + "base64ct", +] + +[[package]] +name = "pkcs8" +version = "0.11.0-rc.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12922b6296c06eb741b02d7b5161e3aaa22864af38dfa025a1a3ba3f68c84577" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" +dependencies = [ + "chacha20", + "getrandom", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serdect" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9af4a3e75ebd5599b30d4de5768e00b5095d518a79fefc3ecbaf77e665d1ec06" +dependencies = [ + "base16ct", + "serde", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "spki" +version = "0.8.0-rc.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8baeff88f34ed0691978ec34440140e1572b68c7dd4a495fd14a3dc1944daa80" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.115" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6523d69017b7633e396a89c5efab138161ed5aafcbc8d3e5c5a42ae38f50495a" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.115" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e3a6c758eb2f701ed3d052ff5737f5bfe6614326ea7f3bbac7156192dc32e67" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.115" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "921de2737904886b52bcbb237301552d05969a6f9c40d261eb0533c8b055fedf" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.115" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a93e946af942b58934c604527337bad9ae33ba1d5c6900bbb41c2c07c2364a93" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84cde8507f4d7cfcb1185b8cb5890c494ffea65edbe1ba82cfd63661c805ed94" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +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" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/hqc-kem/Cargo.toml b/hqc-kem/Cargo.toml new file mode 100644 index 0000000..e4e293b --- /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.0-rc.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 0000000..ca258fb --- /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 = ek.as_ref().to_vec(); + +// Deserialize from bytes +let ek_restored: EncapsulationKey = 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 = + 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(rng: &mut impl rand::CryptoRng) { + let (ek, dk) = HqcKem::

::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 0000000..21ac9a0 --- /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 0000000..176f8ab --- /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 0000000..411fd89 --- /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 0000000..90a2840 --- /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 0000000..22c2b26 --- /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 0000000..4668b07 --- /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 0000000..69e8d3e --- /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 0000000..df693b6 --- /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 0000000..1ca6119 --- /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, Vec) { + // 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, Vec) { + 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, Vec) { + // 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, Vec) { + 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 { + // 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 0000000..a24c9d7 --- /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( + rng: &mut R, + ) -> ( + kem_traits::DecapsulationKey, + kem_traits::EncapsulationKey, + ) { + 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) -> Result { + 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 { + let mut arr = Array::::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( + &self, + rng: &mut R, + ) -> ( + kem_traits::Ciphertext, + kem_traits::SharedKey, + ) + where + R: rand::CryptoRng + ?Sized, + { + let (ss_vec, ct_vec) = crate::kem::encaps(self.as_ref(), <$params>::params(), rng); + + let mut ct = Array::::default(); + ct.as_mut_slice().copy_from_slice(&ct_vec); + + let mut ss = Array::::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 { + 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 { + let sk = self.as_ref(); + let seed_start = sk.len() - crate::params::SEED_BYTES; + let mut arr = Array::::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(rng: &mut R) -> Result::Error> + where + R: rand::TryCryptoRng + ?Sized, + { + let mut seed = [0u8; 32]; + rng.try_fill_bytes(&mut seed)?; + let seed_arr = Array::::from(seed); + Ok(::new(&seed_arr)) + } + } + + // -- DecapsulationKey: Decapsulator -- + impl kem_traits::Decapsulator for DecapsulationKey<$params> { + type Kem = $params; + + fn encapsulation_key(&self) -> &kem_traits::EncapsulationKey { + self.encapsulation_key() + } + } + + // -- DecapsulationKey: Decapsulate -- + impl kem_traits::Decapsulate for DecapsulationKey<$params> { + fn decapsulate( + &self, + ct: &kem_traits::Ciphertext, + ) -> kem_traits::SharedKey { + let ss_vec = crate::kem::decaps(self.as_ref(), ct.as_slice(), <$params>::params()); + let mut ss = Array::::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 0000000..d0d2a27 --- /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; +/// HQC-192 KEM (NIST Level 3). +pub type Hqc192 = HqcKem; +/// HQC-256 KEM (NIST Level 5). +pub type Hqc256 = HqcKem; + +/// 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; + /// HQC-128 decapsulation key. + pub type DecapsulationKey = crate::DecapsulationKey; + /// HQC-128 ciphertext. + pub type Ciphertext = crate::Ciphertext; + /// HQC-128 shared secret. + pub type SharedSecret = crate::SharedSecret; + + /// 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; + /// HQC-192 decapsulation key. + pub type DecapsulationKey = crate::DecapsulationKey; + /// HQC-192 ciphertext. + pub type Ciphertext = crate::Ciphertext; + /// HQC-192 shared secret. + pub type SharedSecret = crate::SharedSecret; + + /// 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; + /// HQC-256 decapsulation key. + pub type DecapsulationKey = crate::DecapsulationKey; + /// HQC-256 ciphertext. + pub type Ciphertext = crate::Ciphertext; + /// HQC-256 shared secret. + pub type SharedSecret = crate::SharedSecret; + + /// 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 0000000..3b2fa33 --- /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 0000000..304c806 --- /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 = + spki::AlgorithmIdentifier { + oid: Self::OID, + parameters: None, + }; +} + +impl AssociatedAlgorithmIdentifier for Hqc192Params { + type Params = AnyRef<'static>; + const ALGORITHM_IDENTIFIER: spki::AlgorithmIdentifier = + spki::AlgorithmIdentifier { + oid: Self::OID, + parameters: None, + }; +} + +impl AssociatedAlgorithmIdentifier for Hqc256Params { + type Params = AnyRef<'static>; + const ALGORITHM_IDENTIFIER: spki::AlgorithmIdentifier = + spki::AlgorithmIdentifier { + oid: Self::OID, + parameters: None, + }; +} + +// AssociatedAlgorithmIdentifier for key types (delegating to P) + +impl

AssociatedAlgorithmIdentifier for EncapsulationKey

+where + P: HqcParams + AssociatedAlgorithmIdentifier>, +{ + type Params = P::Params; + const ALGORITHM_IDENTIFIER: spki::AlgorithmIdentifier = P::ALGORITHM_IDENTIFIER; +} + +impl

AssociatedAlgorithmIdentifier for DecapsulationKey

+where + P: HqcParams + AssociatedAlgorithmIdentifier>, +{ + type Params = P::Params; + const ALGORITHM_IDENTIFIER: spki::AlgorithmIdentifier = P::ALGORITHM_IDENTIFIER; +} + +// --------------------------------------------------------------------------- +// EncodePublicKey (requires alloc) +// --------------------------------------------------------------------------- + +#[cfg(feature = "alloc")] +impl

EncodePublicKey for EncapsulationKey

+where + P: HqcParams + AssociatedAlgorithmIdentifier>, +{ + fn to_public_key_der(&self) -> spki::Result { + 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) +// --------------------------------------------------------------------------- + +impl

TryFrom<::pkcs8::SubjectPublicKeyInfoRef<'_>> for EncapsulationKey

+where + P: HqcParams + AssociatedAlgorithmIdentifier>, +{ + type Error = spki::Error; + + fn try_from(spki: ::pkcs8::SubjectPublicKeyInfoRef<'_>) -> Result { + 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

EncodePrivateKey for DecapsulationKey

+where + P: HqcParams + AssociatedAlgorithmIdentifier>, +{ + fn to_pkcs8_der(&self) -> ::pkcs8::Result { + 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) +// --------------------------------------------------------------------------- + +impl

TryFrom<::pkcs8::PrivateKeyInfoRef<'_>> for DecapsulationKey

+where + P: HqcParams + AssociatedAlgorithmIdentifier>, +{ + type Error = ::pkcs8::Error; + + fn try_from(private_key_info_ref: ::pkcs8::PrivateKeyInfoRef<'_>) -> Result { + 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::Error::KeyMalformed)?; + let seed: [u8; SEED_BYTES] = seed_string + .value + .as_bytes() + .try_into() + .map_err(|_| pkcs8::Error::KeyMalformed)?; + 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 0000000..ae97f1f --- /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, Vec) { + // 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 { + // 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 { + // 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 0000000..dcb50b1 --- /dev/null +++ b/hqc-kem/src/poly.rs @@ -0,0 +1,521 @@ +/// 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) { + #[cfg(target_arch = "x86_64")] + if std::is_x86_feature_detected!("avx2") { + // Safety: AVX2 detected; pointers valid for `size` elements. + unsafe { + return vect_add_avx2(o, v1, v2, size); + } + } + 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) { + #[cfg(target_arch = "x86_64")] + if std::is_x86_feature_detected!("avx2") { + unsafe { + return vect_add_assign_avx2(v, rhs, size); + } + } + 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, +) { + #[cfg(target_arch = "x86_64")] + if std::is_x86_feature_detected!("avx2") { + unsafe { + return karatsuba_add1_avx2(alh, blh, a, b, size_l, size_h); + } + } + 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) { + #[cfg(target_arch = "x86_64")] + if std::is_x86_feature_detected!("avx2") { + unsafe { + return karatsuba_add2_avx2(o, tmp1, tmp2, size_l, size_h); + } + } + 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) { + #[cfg(target_arch = "x86_64")] + if std::is_x86_feature_detected!("avx2") { + unsafe { + return reduce_avx2(o, a, n, vec_n_size_64); + } + } + 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]); + } +} + +// ---- AVX2 SIMD acceleration (x86-64 only) ---- + +#[cfg(target_arch = "x86_64")] +#[target_feature(enable = "avx2")] +unsafe fn vect_add_avx2(o: &mut [u64], v1: &[u64], v2: &[u64], size: usize) { + use std::arch::x86_64::*; + let chunks = size / 4; + unsafe { + let p1 = v1.as_ptr() as *const __m256i; + let p2 = v2.as_ptr() as *const __m256i; + let po = o.as_mut_ptr() as *mut __m256i; + for i in 0..chunks { + _mm256_storeu_si256( + po.add(i), + _mm256_xor_si256(_mm256_loadu_si256(p1.add(i)), _mm256_loadu_si256(p2.add(i))), + ); + } + } + for i in (chunks * 4)..size { + o[i] = v1[i] ^ v2[i]; + } +} + +#[cfg(target_arch = "x86_64")] +#[target_feature(enable = "avx2")] +unsafe fn vect_add_assign_avx2(v: &mut [u64], rhs: &[u64], size: usize) { + use std::arch::x86_64::*; + let chunks = size / 4; + unsafe { + let pv = v.as_mut_ptr() as *mut __m256i; + let pr = rhs.as_ptr() as *const __m256i; + for i in 0..chunks { + _mm256_storeu_si256( + pv.add(i), + _mm256_xor_si256( + _mm256_loadu_si256(pv.add(i) as *const __m256i), + _mm256_loadu_si256(pr.add(i)), + ), + ); + } + } + for i in (chunks * 4)..size { + v[i] ^= rhs[i]; + } +} + +#[cfg(target_arch = "x86_64")] +#[target_feature(enable = "avx2")] +unsafe fn karatsuba_add1_avx2( + alh: &mut [u64], + blh: &mut [u64], + a: &[u64], + b: &[u64], + size_l: usize, + size_h: usize, +) { + use std::arch::x86_64::*; + let chunks = size_h / 4; + for i in 0..chunks { + let idx = i * 4; + unsafe { + let a_lo = _mm256_loadu_si256(a.as_ptr().add(idx) as *const __m256i); + let a_hi = _mm256_loadu_si256(a.as_ptr().add(idx + size_l) as *const __m256i); + _mm256_storeu_si256( + alh.as_mut_ptr().add(idx) as *mut __m256i, + _mm256_xor_si256(a_lo, a_hi), + ); + let b_lo = _mm256_loadu_si256(b.as_ptr().add(idx) as *const __m256i); + let b_hi = _mm256_loadu_si256(b.as_ptr().add(idx + size_l) as *const __m256i); + _mm256_storeu_si256( + blh.as_mut_ptr().add(idx) as *mut __m256i, + _mm256_xor_si256(b_lo, b_hi), + ); + } + } + for i in (chunks * 4)..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]; + } +} + +#[cfg(target_arch = "x86_64")] +#[target_feature(enable = "avx2")] +#[allow(clippy::cast_ptr_alignment)] +unsafe fn karatsuba_add2_avx2( + o: &mut [u64], + tmp1: &mut [u64], + tmp2: &[u64], + size_l: usize, + size_h: usize, +) { + use std::arch::x86_64::*; + let len1 = 2 * size_l; + let len2 = 2 * size_h; + + // tmp1[i] ^= o[i] for i in 0..2*size_l + let chunks1 = len1 / 4; + for i in 0..chunks1 { + let idx = i * 4; + unsafe { + let pt = tmp1.as_mut_ptr().add(idx) as *mut __m256i; + _mm256_storeu_si256( + pt, + _mm256_xor_si256( + _mm256_loadu_si256(pt as *const __m256i), + _mm256_loadu_si256(o.as_ptr().add(idx) as *const __m256i), + ), + ); + } + } + for i in (chunks1 * 4)..len1 { + tmp1[i] ^= o[i]; + } + + // tmp1[i] ^= tmp2[i] for i in 0..2*size_h + let chunks2 = len2 / 4; + for i in 0..chunks2 { + let idx = i * 4; + unsafe { + let pt = tmp1.as_mut_ptr().add(idx) as *mut __m256i; + _mm256_storeu_si256( + pt, + _mm256_xor_si256( + _mm256_loadu_si256(pt as *const __m256i), + _mm256_loadu_si256(tmp2.as_ptr().add(idx) as *const __m256i), + ), + ); + } + } + for i in (chunks2 * 4)..len2 { + tmp1[i] ^= tmp2[i]; + } + + // o[i + size_l] ^= tmp1[i] for i in 0..2*size_l + for i in 0..chunks1 { + let idx = i * 4; + unsafe { + let po = o.as_mut_ptr().add(idx + size_l) as *mut __m256i; + _mm256_storeu_si256( + po, + _mm256_xor_si256( + _mm256_loadu_si256(po as *const __m256i), + _mm256_loadu_si256(tmp1.as_ptr().add(idx) as *const __m256i), + ), + ); + } + } + for i in (chunks1 * 4)..len1 { + o[i + size_l] ^= tmp1[i]; + } +} + +#[cfg(target_arch = "x86_64")] +#[target_feature(enable = "avx2")] +unsafe fn reduce_avx2(o: &mut [u64], a: &[u64], n: usize, vec_n: usize) { + use std::arch::x86_64::*; + let shift = (n & 0x3f) as i64; + let inv_shift = 64 - shift; + let chunks = vec_n / 4; + + unsafe { + let shift_v = _mm_set_epi64x(0, shift); + let inv_shift_v = _mm_set_epi64x(0, inv_shift); + + for i in 0..chunks { + let idx = i * 4; + let base = _mm256_loadu_si256(a.as_ptr().add(idx) as *const __m256i); + let hi_prev = _mm256_loadu_si256(a.as_ptr().add(idx + vec_n - 1) as *const __m256i); + let hi_next = _mm256_loadu_si256(a.as_ptr().add(idx + vec_n) as *const __m256i); + let r = _mm256_srl_epi64(hi_prev, shift_v); + let carry = _mm256_sll_epi64(hi_next, inv_shift_v); + _mm256_storeu_si256( + o.as_mut_ptr().add(idx) as *mut __m256i, + _mm256_xor_si256(base, _mm256_xor_si256(r, carry)), + ); + } + } + // Scalar remainder + let shift_s = n & 0x3f; + for i in (chunks * 4)..vec_n { + let r = a[i + vec_n - 1] >> shift_s; + let carry = if i + vec_n < a.len() { + a[i + vec_n] << (64 - shift_s) + } else { + 0 + }; + o[i] = a[i] ^ r ^ carry; + } +} + +/// 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 0000000..46be489 --- /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 0000000..7466f41 --- /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 0000000..f10e3f2 --- /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 0000000..f71610e --- /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: ::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 0000000..7972b6a --- /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 0000000..7f913ea --- /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 TryFrom<&[u8]> for $name

{ + type Error = Error; + + fn try_from(bytes: &[u8]) -> Result { + 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 TryFrom> for $name

{ + type Error = Error; + + fn try_from(bytes: Vec) -> Result { + Self::try_from(bytes.as_slice()) + } + } + + impl TryFrom<&Vec> for $name

{ + type Error = Error; + + fn try_from(bytes: &Vec) -> Result { + Self::try_from(bytes.as_slice()) + } + } + + impl TryFrom> for $name

{ + type Error = Error; + + fn try_from(bytes: Box<[u8]>) -> Result { + Self::try_from(bytes.as_ref()) + } + } + + impl AsRef<[u8]> for $name

{ + fn as_ref(&self) -> &[u8] { + &self.bytes + } + } + }; +} + +/// HQC encapsulation key (public key). +#[derive(Clone)] +pub struct EncapsulationKey { + pub(crate) bytes: Vec, + pub(crate) _marker: PhantomData

, +} + +/// HQC decapsulation key (secret key). +#[derive(Clone)] +pub struct DecapsulationKey { + pub(crate) bytes: Vec, + pub(crate) ek: EncapsulationKey

, + pub(crate) _marker: PhantomData

, +} + +/// HQC ciphertext. +#[derive(Clone)] +pub struct Ciphertext { + pub(crate) bytes: Vec, + pub(crate) _marker: PhantomData

, +} + +/// HQC shared secret. +#[derive(Clone)] +pub struct SharedSecret { + pub(crate) bytes: Vec, + pub(crate) _marker: PhantomData

, +} + +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 EncapsulationKey

{ + pub(crate) fn from_vec(bytes: Vec) -> Self { + debug_assert_eq!(bytes.len(), P::PK_BYTES); + Self { + bytes, + _marker: PhantomData, + } + } +} + +#[cfg(any(feature = "kem", feature = "pkcs8"))] +impl DecapsulationKey

{ + pub(crate) fn from_vec(bytes: Vec) -> 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(PhantomData

); + +impl DecapsulationKey

{ + /// Get the encapsulation (public) key corresponding to this decapsulation key. + pub fn encapsulation_key(&self) -> &EncapsulationKey

{ + &self.ek + } +} + +impl TryFrom<&[u8]> for DecapsulationKey

{ + type Error = Error; + + fn try_from(bytes: &[u8]) -> Result { + 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 core::fmt::Debug for EncapsulationKey

{ + 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 core::fmt::Debug for DecapsulationKey

{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let name: String = format!("{}::DecapsulationKey", P::NAME); + f.debug_struct(&name).finish() + } +} + +impl core::fmt::Debug for Ciphertext

{ + 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 core::fmt::Debug for SharedSecret

{ + 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 PartialEq for EncapsulationKey

{ + fn eq(&self, other: &Self) -> bool { + self.bytes == other.bytes + } +} + +impl Eq for EncapsulationKey

{} + +impl PartialEq for Ciphertext

{ + fn eq(&self, other: &Self) -> bool { + self.bytes == other.bytes + } +} + +impl Eq for Ciphertext

{} + +// --------------------------------------------------------------------------- +// ConstantTimeEq / PartialEq / Eq (SharedSecret only) +// --------------------------------------------------------------------------- + +impl ConstantTimeEq for SharedSecret

{ + fn ct_eq(&self, other: &Self) -> subtle::Choice { + self.bytes.as_slice().ct_eq(other.bytes.as_slice()) + } +} + +impl PartialEq for SharedSecret

{ + fn eq(&self, other: &Self) -> bool { + self.ct_eq(other).into() + } +} + +impl Eq for SharedSecret

{} + +// --------------------------------------------------------------------------- +// Zeroize + Drop (secret types) +// --------------------------------------------------------------------------- + +impl Zeroize for DecapsulationKey

{ + fn zeroize(&mut self) { + self.bytes.zeroize(); + } +} + +impl Drop for DecapsulationKey

{ + fn drop(&mut self) { + self.zeroize(); + } +} + +impl Zeroize for SharedSecret

{ + fn zeroize(&mut self) { + self.bytes.zeroize(); + } +} + +impl Drop for SharedSecret

{ + fn drop(&mut self) { + self.zeroize(); + } +} + +// --------------------------------------------------------------------------- +// KEM operations (feature-gated) +// --------------------------------------------------------------------------- + +#[cfg(feature = "kgen")] +impl HqcKem

{ + /// Generate an HQC key pair. + pub fn generate_key( + rng: &mut impl rand::CryptoRng, + ) -> (EncapsulationKey

, DecapsulationKey

) { + 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

, DecapsulationKey

) { + 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 EncapsulationKey

{ + /// Encapsulate: produce a ciphertext and shared secret. + pub fn encapsulate(&self, rng: &mut impl rand::CryptoRng) -> (Ciphertext

, SharedSecret

) { + 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

, SharedSecret

), 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 DecapsulationKey

{ + /// Decapsulate: recover shared secret from ciphertext. + pub fn decapsulate(&self, ct: &Ciphertext

) -> SharedSecret

{ + 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 serde::Serialize for $name

{ + fn serialize(&self, s: S) -> Result { + 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

{ + fn deserialize>(d: D) -> Result { + 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

{ + fn deserialize>(d: D) -> Result { + 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 0000000..a9332f4 --- /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, +} + +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 { + let mut buf = [0u8; 4]; + self.try_fill_bytes(&mut buf)?; + Ok(u32::from_le_bytes(buf)) + } + + fn try_next_u64(&mut self) -> Result { + 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, + pk: Vec, + sk: Vec, + ct: Vec, + ss: Vec, +} + +/// 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"); +}