diff --git a/cli/src/mldsa_cmd.rs b/cli/src/mldsa_cmd.rs index beb92c6..070af7e 100644 --- a/cli/src/mldsa_cmd.rs +++ b/cli/src/mldsa_cmd.rs @@ -1,4 +1,4 @@ -//! Work in progress. +//! Work in progress. //! TODO: Use generic macros to eliminate duplicated code. use crate::helpers::{parse_seed, read_from_file, read_from_file_or_stdin, write_bytes_or_hex}; diff --git a/crypto/core/src/key_material.rs b/crypto/core/src/key_material.rs index dc3244d..fb95d71 100644 --- a/crypto/core/src/key_material.rs +++ b/crypto/core/src/key_material.rs @@ -547,7 +547,7 @@ impl KeyMaterialTrait for KeyMaterial { } self.security_strength = strength; - + Ok(()) } diff --git a/crypto/factory/src/rng_factory.rs b/crypto/factory/src/rng_factory.rs index c316475..492efec 100644 --- a/crypto/factory/src/rng_factory.rs +++ b/crypto/factory/src/rng_factory.rs @@ -30,7 +30,7 @@ //! let output: Vec = h.hash(data); //! ``` //! Equivalently, it may be invoked by passing a string instead of using the constant: -//! +//! //! ``` //! use bouncycastle_factory::AlgorithmFactory; //! use bouncycastle_core::traits::Hash; diff --git a/crypto/hex/src/lib.rs b/crypto/hex/src/lib.rs index 3b53b45..923151d 100644 --- a/crypto/hex/src/lib.rs +++ b/crypto/hex/src/lib.rs @@ -3,14 +3,14 @@ //! This one is implemented using constant-time operations in the conversions //! from Strings to byte values, so it is safe to use on cryptographic secret values. //! -//! It should just work as expected: -//! encode takes any bytes-like rust type and returns a String, +//! It should just work as expected: +//! encode takes any bytes-like rust type and returns a String, //! decode takes a String (which can be in any bytes-like container) and returns a `Vec`. //! //! Moreover, the API of this crate is intended to mirror that of the public `hex` crate, //! so you should generally be able to swap `use hex` for `use bouncycastle_hex` and all the function //! calls and behaviours should work as expected. -//! +//! //! ``` //! use bouncycastle_hex as hex; //! @@ -72,7 +72,7 @@ pub fn encode_out>(input: T, out: &mut [u8]) -> Result::is_within_range(c as i64, 0, 9); let in_af = Condition::::is_within_range(c as i64, 10, 15); - // TODO: redo this once we have ct::u8 implemented + // TODO: redo this once we have ct::u8 implemented // The i64 is wasteful let c_09: i64 = '0' as i64 + (c as i64); @@ -111,7 +111,7 @@ pub fn decode_out>(input: T, out: &mut [u8]) -> Result { @@ -157,7 +157,7 @@ pub fn decode_out>(input: T, out: &mut [u8]) -> Result::is_within_range(b as i64, 65, 70); - // TODO: redo this once we have ct::u8 implemented + // TODO: redo this once we have ct::u8 implemented // The i64 is wasteful let c_09: i64 = b as i64 - ('0' as i64); diff --git a/crypto/mldsa-lowmemory/src/aux_functions.rs b/crypto/mldsa-lowmemory/src/aux_functions.rs index 4d91dc5..2726420 100644 --- a/crypto/mldsa-lowmemory/src/aux_functions.rs +++ b/crypto/mldsa-lowmemory/src/aux_functions.rs @@ -471,7 +471,7 @@ pub(crate) fn sample_in_ball( let mut j = [0u8]; for i in (N - TAU as usize)..N { // 7: (ctx, ๐‘—) โ† H.Squeeze(ctx, 1) - // Note: At first, it might seem to be faster to pre-squeeze a buffer outside the loop. + // Note: At first, it might seem to be faster to pre-squeeze a buffer outside the loop. // However, after experimentation and testing, the difference is not noticeable. h.squeeze_out(&mut j); @@ -567,7 +567,7 @@ pub(crate) fn rej_bounded_poly(rho: &[u8; 64], nonce: &[u8; 2] // SHAKE is fairly inefficient if only 3 bytes are squeezed at a time, so the implementation does a block instead. // size is not a limitation as long as it is a multiple of 3. // 312 seems to be the sweet spot after some experimentation - // which is possibly also related with the average rejection rate. + // which is possibly also related with the average rejection rate. // Also, 312 is a multiple of 8 (efficient for SHAKE) let mut z_arr = [0u8; 312]; h.squeeze_out(&mut z_arr); diff --git a/crypto/mldsa-lowmemory/src/polynomial.rs b/crypto/mldsa-lowmemory/src/polynomial.rs index 2e672b5..95e3387 100644 --- a/crypto/mldsa-lowmemory/src/polynomial.rs +++ b/crypto/mldsa-lowmemory/src/polynomial.rs @@ -95,7 +95,7 @@ impl Polynomial { pub(crate) fn check_norm(&self) -> bool { // Fine that this is not constant-time (returns true early) because it is used in a rejection loop. - // IE the early quit here leads to rejection and continuing to the top of the rejection loop, or failing + // IE the early quit here leads to rejection and continuing to the top of the rejection loop, or failing // the signature validation. // So the i32 that was just checked in a non-constant-time manner is about to get thrown away. diff --git a/crypto/mldsa/src/hash_mldsa.rs b/crypto/mldsa/src/hash_mldsa.rs index af38aff..5e1b535 100644 --- a/crypto/mldsa/src/hash_mldsa.rs +++ b/crypto/mldsa/src/hash_mldsa.rs @@ -1097,7 +1097,7 @@ impl< /// Note that the PH expected here *is not the same* as the `mu` computed by [`MuBuilder`]. /// To make use of this function, the user needs to compute a straight hash of the message using - /// the same hash function as the indicated in the HashML-DSA variant; + /// the same hash function as the indicated in the HashML-DSA variant; /// for example: SHA256 for HashMDSA44_with_SHA256; SHA512 for HashMLDSA65_with_SHA512; etc. fn sign_ph_out( sk: &SK, diff --git a/crypto/mldsa/src/lib.rs b/crypto/mldsa/src/lib.rs index 91852f1..15fa740 100644 --- a/crypto/mldsa/src/lib.rs +++ b/crypto/mldsa/src/lib.rs @@ -116,11 +116,10 @@ //! //! `mu`, `ph`, and `ctx` are binding values that the verifier must reproduce. This means that getting //! them wrong does not compromise security, it just yields a signature the intended -//! verifier won't accept (a correctness/interoperability failure). +//! verifier won't accept (a correctness/interoperability failure). //! One caveat: `ctx` can still be security-relevant at the protocol level (domain separation, replay and //! cross-protocol binding), so choosing it incorrectly can weaken those properties. - #![no_std] #![forbid(unsafe_code)] #![forbid(missing_docs)] diff --git a/crypto/mldsa/tests/mldsa_tests.rs b/crypto/mldsa/tests/mldsa_tests.rs index a07d2e9..33aa8f9 100644 --- a/crypto/mldsa/tests/mldsa_tests.rs +++ b/crypto/mldsa/tests/mldsa_tests.rs @@ -1083,9 +1083,9 @@ struct Kat { } // generated by hand against bc-java -// This is almost tgId=1, tcId=1 from +// This is almost tgId=1, tcId=1 from // https://raw.githubusercontent.com/bcgit/bc-test-data/refs/heads/main/pqc/crypto/mldsa/ML-DSA-sigGen.txt -// with the difference being that an empty ctx is added, instead of testing sign_internal directly, +// with the difference being that an empty ctx is added, instead of testing sign_internal directly, // and the signature value is run against bc-java const MLDSA44_KAT1: Kat = Kat { _parameter_set: "ML-DSA-44", @@ -1096,9 +1096,9 @@ const MLDSA44_KAT1: Kat = Kat { signature: "2bddcee4a9ac1b9d19bc1531365c5613e48b95a530339c52f5fc0b671ee01b6587fe08b290c191f82eace640fae216cca90f40fa93bb309e5ff53afc5a042050bffeb69d4e0041c34fd334a7b576c6ebae68042b315fe78f84300dc011cdb144252a06f35c9a2b0a275d00552f890361d1e7b7439572097d1f3d5833b98b751412deb3c0df23ee3a30782919444bcbcc9ac25c645180c8d1a9fe693086cba4779d6e31b1e5fd1a0ccaeb055c471ef065a273676d78c6e7feebfc607c5578107ac27765345363af57f77431e306d9407ce365708d813e44d3107c108f8c5f2848913a4099f6a0ce2ebf80c7414236d4edc07fee79efe1ab6fe70217bec958ea48221fb6b87cef6f5762b41a9c698fbb45f5994969bee21e40f20c95f9a18389e8b49d6ecccdae9f568313448b5162c981bc9ff320753aede977b15f7ddc68bba076a07deb68ac36826e70d80752fac0356d507b3e283b350a17b015f76c71314f7c2086e44601c4d9c707c99a36e55716fd34384a218242a69876c8dea9f6ec28c40189ee2b8608040a96a813f38240dc85a511b3cebab1ea893270e730aef2e29406fcc1f6826101f1361168ceb3f1632e8ee505143e0f031e744928c1e41eab923ebbfce5e9f159fd160db737b013759382274a1d9409554ab06eb72b5ce2a710d8b08f163df991c956abc823d21b0a6d9d7ae484d6cdb2e04de7a282ef317f488e20d40e4e67cbf05d1528c0ce261e3a65163ad518661989484bc964da21122a6c95ef7036b3273f93833d068d9a2c7933e19ad2fde8378c286ad57f7e22c8aa4153718f356b46b34ca4a986e6e9ef3ddb206693acf3b08c0aa5118c8efbb6a99a0fc0674f6228e2f6f53e229129d4b6c0fd6455e28587f0f169270d044398e2c377ab6f985f7f2235d68192d031f39251f9f0270451beda2d29b654511b73bce0f30174a606a1100f4fc984959704ba0e2df2bdf642ba3b0241ca9889009ae575540c71747246835018ba8faeb473b39e4ede5a6fbe0165a4db2e90739cb051f3e5c1c0ce159539758569ecc8b67a759a2e786d5b4e96834db0dd460ab1c5c7ac8f3cf19596979108dfbdc6c8798e7342026b7571ba64fa86d68e1d5179af5768a7e22db76f3a319193d04eeea626d03d4be7dd83ed6c46e2149a1e2428e60f6566833726bcc93d0b342ff4245665b54167b013b02165d29fb64055112c5d800e853bf28bf149da2284e49dc1c5f478f490ce1002f16431564534244cd9c1e0bd12ea9209efe572afb7effc0153e5b2066b1af0dd05a2d9da13308c90a0244ad95a33a64d07a75d7596e46cdcfd154e18f7677ef16b979a1f431eb59279c65a3a31c5429ceebfcc869cf538722a82e6e765bd6a3042dc825684ad4163e054c113b47276b894580db17c92f0859c240b6812716e408a557c1d7ac8cfc3abf9b43f759bb2a13c903bba936206496fed37696171717cb8995b837e869052c9c25348698617c70e98966d30f56bb5bf41369c0131de4178637c5684a64992b50f9ab9a16b51752e14ea521d5bd42bacdfcf100085629796d22bcaa6a7830f9b5859d75b290a0db031bd51b77fa7911eb17d46dcf16d45e3b1ce8fb7dc19ba969a724e43ccce9487c143302c79fb208b2ab0ea1060af3809d5397c11cc0a79ed39ab06c0ef7bceb8e094ec0e3ea52937442ccc176a31b07a52d7de84c0599dbe736cecfacbb41cac206ec3dc20603606a14a1db3ccad8037a540f3213ee9337b529f447ed2c2e287760973b8175f70c78423ace6179757c519c14a0d7b81af00646afde573a35e909ef9660623cd3651b63b79be508698888e1d3bc87344067dcc099416616c273d5ea0e41a8723d11c5ec49e0c6019f6576a2e87d0237c3f64d49afbaf09fc818829d5d0fcb4336e08503d570dc3c96fa71fd7b2d4f51d0d1acc515c2c4dad23896273b2616f7d848816c5cc17f7bb2ed5f952a708c38eb13f0fdd8f14ec71893d3f19ccc8aa15fed848583093f4d8246f36db300c9cdd5d63b507641e4d2ba9ec284d17f4a05696be00da371bd5bab0ba56a13451278dbdeed02b4093e482b96269707b09a10dba9ca1dffb4e4185f9e392ebabcea1f9d26c037d9c0dcd71f2bc13faf559d195ef0d8be7baa0cf8aa105a8069356aa2287ec1003e0759f27b02772d4855bf0b7e0b3e97b7f77bd3119669695cc706090991e6a542eb7a28547978ba8a05d9103a90789108fa15898d14982bd0d5d098019700b37939f9f0d66e78af49c6318b886ba2a9101c072bb1e7bdbdb7319eb38e3320b4fbb1fcb28e8e7fac0bd493faf3c0547214465d55ca212dae99dc53addc3378b7e7b93acbdc1c9787149ed0214cd9846852ed7c18b23ab0ae8b7afbb725d44997a38422ff17b1dbeb22e2387094e0bc59496786114d0bb398f36fdfb06c70ff0ce47e9c3eb8c32c22062ccd5306026b606a9e9c628a377f0efd71087c95b3c1ffbdec8a91f311fbba4793d3d3fbf350e6a4a491d74ab7fb0cb66afa0d66177853df464f0175cdee4f97a4e620366aa18c2d04ebab82ff31ec07722fd53e0b4926973dd41d10422747261d8772b18ab55e0bbdcb89e224a5fc2679c2b729aaa4e1a78f95cf68af3562b98f0586d02134464f87dd15b843451a9160c5f4704c994a32259ca623c937431cfe55ee97d736916ac3e7ef831a1b6978539ac6c3304de2f43b3b208f73d071d17fd5cae631f617929468fc59d529deab1c0080a85ded180f9b7029376059c5ca3ba2eab9ee556a74373eadb5983f5990146b04bd255f2380450864e33c478cfe42072eeaabf8032f2c22fbf111407bf2cc41f6cc55596cca62a69303089c3ec231f42a8358d8bce315debce8cd4cea4aa478fba3476b5252e2d64da6b70d6ea0c1b4a99abebbd194e62e25442e7ecffc788710ac6dd1da815a0a5ef8dcef34ebcc90c6f29372875d664c2a06f3485dad8bd00d94837f417412399f9f085d8666fcaf38d38620897e2d06c7399eb28c5db0ffa42a233fdc6732c3e525bd49771aad03348aad068a5e729565c10d343a10c5cd6e530994c9a400354de3af3b39d20cf2b54fc0c9bde4b6f520688694d9b53c3628f1744b61271383d852219d8afae7a284d2f0b042f2fb70778b53d30876b5a031904a241e5a1741ff2e88bf8faa60472ba66111f0560ef127ee86d24f2c502fcff7575696f7f450109776f0f5e1bbd77a48952697fb2f8657516cc061de2bcc6aff9b861356b97e576f78abab1d3acb70d14a84b7858c22b2a0ee17e1231a2da3738018205091b263c42597ea6d3d7d9dae3fa030a13162243494c4e7f8fa1a2d8e2ff11153a41474a63646770717487919da9bac9d2d3e3070f23455d686a7dbcc9de00000000000000000000000000000000000f1f343f", }; -// This is almost tgId=1, tcId=1 from +// This is almost tgId=1, tcId=1 from // https://raw.githubusercontent.com/bcgit/bc-test-data/refs/heads/main/pqc/crypto/mldsa/ML-DSA-sigGen.txt -// with the difference being that an empty ctx is added, instead of testing sign_internal directly, +// with the difference being that an empty ctx is added, instead of testing sign_internal directly, // and the signature value is run against bc-java const MLDSA65_KAT1: Kat = Kat { _parameter_set: "ML-DSA-65", @@ -1110,9 +1110,9 @@ const MLDSA65_KAT1: Kat = Kat { }; // generated by hand against bc-java -// This is almost tgId=1, tcId=1 from +// This is almost tgId=1, tcId=1 from // https://raw.githubusercontent.com/bcgit/bc-test-data/refs/heads/main/pqc/crypto/mldsa/ML-DSA-sigGen.txt -// with the difference being that an empty ctx is added, instead of testing sign_internal directly, +// with the difference being that an empty ctx is added, instead of testing sign_internal directly, // and the signature value is run against bc-java const MLDSA87_KAT1: Kat = Kat { _parameter_set: "ML-DSA-87", diff --git a/crypto/mlkem-lowmemory/src/aux_functions.rs b/crypto/mlkem-lowmemory/src/aux_functions.rs index 7df893d..b548f44 100644 --- a/crypto/mlkem-lowmemory/src/aux_functions.rs +++ b/crypto/mlkem-lowmemory/src/aux_functions.rs @@ -62,7 +62,7 @@ pub(crate) fn byte_decode(B: &[u8; PACK_L for j in 0..d { // select the next bit, according to bitcount, then shift it up by j // there is supposed to be a `mod m` here, but that shouldn't matter as they are being checked below - F[i] |= (((B[(i * d + j) / 8] >> (i * d + j) % 8) & 1) as i16) << j; + F[i] |= (((B[(i * d + j) / 8] >> (i * d + j) % 8) & 1) as i16) << j; } // assert the mod m // These are relaxed because they are being checked above in MLKEMPublicKey::pk_decode() diff --git a/crypto/mlkem-lowmemory/src/lib.rs b/crypto/mlkem-lowmemory/src/lib.rs index 207da32..ae93345 100644 --- a/crypto/mlkem-lowmemory/src/lib.rs +++ b/crypto/mlkem-lowmemory/src/lib.rs @@ -208,16 +208,16 @@ //! There are, however, a few exceptions worth mentioning. //! //! If using a [`MLKEM::keygen_from_seed`], then it is your responsibility to ensure that the seed is -//! cryptographically random and unpredictable at a security strength that matches the MLKEM parameter set. +//! cryptographically random and unpredictable at a security strength that matches the MLKEM parameter set. //! //! Also, [`MLKEM::encaps_internal`] requires the encapsulation randomness to be provided, so the ciphertext //! will only be as strong as the randomness that you provide. -//! +//! //! A note about cryptographic side-channel attacks: considerable effort has been expended to attempt //! to make this implementation constant-time, which generally means that the core mathematical algorithm //! code that handles secret data uses bitshift-and-xor type constructions instead of if-and-loop //! constructions. That should give this implementation reasonably good resistance to timing and -//! power analysis key extraction attacks, however: +//! power analysis key extraction attacks, however: //! A) this is a "best-effort" and not formally verified, and //! B) the Rust compiler does not guarantee constant-time behaviour no matter how good the design is code, //! so like all Safe Rust code (ie Rust code that does not include inline assembly), @@ -227,7 +227,7 @@ #![no_std] #![forbid(missing_docs)] #![forbid(unsafe_code)] -// These are because variable names need to be matched exactly against FIPS 204, +// These are because variable names need to be matched exactly against FIPS 204, // for example both 'K' and 'k', or 'A' and 'a' are used and have specific meanings. // linter needs to be instructed to ignore these cases #![allow(non_snake_case)] diff --git a/crypto/mlkem/src/aux_functions.rs b/crypto/mlkem/src/aux_functions.rs index 88966d7..0999837 100644 --- a/crypto/mlkem/src/aux_functions.rs +++ b/crypto/mlkem/src/aux_functions.rs @@ -301,9 +301,8 @@ pub(crate) fn barrett_reduce(a: i16) -> i16 { a - (((t as i32) * q as i32) as i16) } - -// Not currently used. It is left here as a reference since it's useful for debugging if it's -// necessary to output values that are normalized to [0,q] to compare against intermediate results +// Not currently used. It is left here as a reference since it's useful for debugging if it's +// necessary to output values that are normalized to [0,q] to compare against intermediate results // from other libraries. // pub(super) fn cond_sub_q(a: i16) -> i16 { // let tmp = a - q; diff --git a/crypto/mlkem/src/polynomial.rs b/crypto/mlkem/src/polynomial.rs index 6617e46..4002528 100644 --- a/crypto/mlkem/src/polynomial.rs +++ b/crypto/mlkem/src/polynomial.rs @@ -9,7 +9,7 @@ use crate::mlkem::{N, q}; /// A polynomial over the ML-KEM ring. /// -/// Dev note: The following structure does not necessarily need to be declared as public. +/// Dev note: The following structure does not necessarily need to be declared as public. /// There is no real scenario where this function needs to be called directly. /// However, in order to test the Debug and Display traits, it is necessary to use STD, so those /// can't be tested from inline tests in this file and the real unit tests are in a different crate. @@ -46,8 +46,8 @@ impl Polynomial { Self { coeffs: [0i16; N] } } - /// Encodes a 32-byte message `m` into a `Polynomial`, implementing the message - /// encoding step of K-PKE.Encrypt `Decompress_1(ByteDecode_1(m))`, + /// Encodes a 32-byte message `m` into a `Polynomial`, implementing the message + /// encoding step of K-PKE.Encrypt `Decompress_1(ByteDecode_1(m))`, /// (FIPS 203, Alg. 14). Each message bit becomes one coefficient: `Decompress_1` /// (ยง4.2.1) maps bit `1` to `โŒˆq/2โŒ‰ = (q + 1) / 2 = 1665` (for `q = 3329`) and bit /// `0` to `0`, placing a set bit at the point farthest from `0` to maximize the @@ -67,25 +67,25 @@ impl Polynomial { w } - /// Decodes a `Polynomial` into its 32-byte message `m`, implementing the message + /// Decodes a `Polynomial` into its 32-byte message `m`, implementing the message /// recovery step of K-PKE.Decrypt `ByteEncode_1(Compress_1(self))`, /// (FIPS 203, Alg. 15). Each coefficient yields one message bit: `Compress_1` /// (ยง4.2.1) sets the bit when the coefficient lies nearer `q/2` than `0`, i.e. in /// the central interval `[833, 2496]` for `q = 3329`. The decision is computed - /// branchlessly and the bits are packed LSB-first. - /// Coefficients are expected to already be canonical in `[0, q]`: the unsigned - /// interval test is not periodic mod `q`, so the caller reduces beforehand (`poly_reduce()` + /// branchlessly and the bits are packed LSB-first. + /// Coefficients are expected to already be canonical in `[0, q]`: the unsigned + /// interval test is not periodic mod `q`, so the caller reduces beforehand (`poly_reduce()` /// in `pke_decrypt`) and no reduction is repeated here. pub(crate) fn to_msg(self) -> [u8; 32] { - const LOWER: i32 = q as i32 >> 2; // โŒŠq/4โŒ‹ = 832 - const UPPER: i32 = q as i32 - LOWER; // q - โŒŠq/2โŒ‹ = 2497 + const LOWER: i32 = q as i32 >> 2; // โŒŠq/4โŒ‹ = 832 + const UPPER: i32 = q as i32 - LOWER; // q - โŒŠq/2โŒ‹ = 2497 let mut msg = [0u8; 32]; // Using full reduce() might be expected here. - // However, this function is only called by pke_decrypt (see mlkem.rs), which performs a + // However, this function is only called by pke_decrypt (see mlkem.rs), which performs a // reduction on every coefficient of the polynomial immediately prior to the call. - // For completeness, testing against the bc-test-data set of KATs shows that everything passes + // For completeness, testing against the bc-test-data set of KATs shows that everything passes // without modular reduction. // self.cond_sub_q(); @@ -101,8 +101,8 @@ impl Polynomial { msg } - // Not currently used. It is left here as a reference since it's useful for debugging if it's - // necessary to output values that are normalized to [0,q] to compare against intermediate results + // Not currently used. It is left here as a reference since it's useful for debugging if it's + // necessary to output values that are normalized to [0,q] to compare against intermediate results // from other libraries. // pub(crate) fn conditional_add_q(&mut self) { // for x in self.0.iter_mut() { @@ -157,7 +157,7 @@ impl Polynomial { // bc-java has a cond_sub_q() here, however, it is not needed // The reason for this is because a modular reduction is performed immediately // prior to calling pack_ciphertext in mlkem.rs - // This can be corroborated by running the corresponding unit tests + // This can be corroborated by running the corresponding unit tests // let mut s = self.clone(); // s.cond_sub_q(); @@ -250,8 +250,8 @@ impl Polynomial { v } - // Not currently used. It is left here as a reference since it's useful for debugging if it's - // necessary to output values that are normalized to [0,q] to compare against intermediate results + // Not currently used. It is left here as a reference since it's useful for debugging if it's + // necessary to output values that are normalized to [0,q] to compare against intermediate results // from other libraries. // pub(crate) fn cond_sub_q(&mut self) { // for i in 0..N { @@ -357,8 +357,8 @@ pub fn base_mult_montgomery(a: &Polynomial, b: &Polynomial) -> Polynomial { r } -// Not currently used. It is left here as a reference since it's useful for debugging if it's -// necessary to output values that are normalized to [0,q] to compare against intermediate results +// Not currently used. It is left here as a reference since it's useful for debugging if it's +// necessary to output values that are normalized to [0,q] to compare against intermediate results // from other libraries. // /// if a is in \[-q..0], then it shifts it up by q to be in \[0..q] // pub(crate) fn conditional_add_q(a: i16) -> i16 { diff --git a/crypto/mlkem/tests/mlkem_key_tests.rs b/crypto/mlkem/tests/mlkem_key_tests.rs index b3e1e4f..24930d2 100644 --- a/crypto/mlkem/tests/mlkem_key_tests.rs +++ b/crypto/mlkem/tests/mlkem_key_tests.rs @@ -61,7 +61,6 @@ mod mlkem_key_tests { // 2) whether it calculates H(ek) properly from a private key // 3) whether it rejects a private key if the H(ek) is wrong - let seed = KeyMaterial512::from_bytes_as_type( &hex::decode( "000102030405060708090a0b0c0d0e0f diff --git a/crypto/rng/src/hash_drbg80090a.rs b/crypto/rng/src/hash_drbg80090a.rs index 5dc1013..be70cb8 100644 --- a/crypto/rng/src/hash_drbg80090a.rs +++ b/crypto/rng/src/hash_drbg80090a.rs @@ -216,7 +216,7 @@ impl Sp80090ADrbg for HashDRBG80090A { "Provided seed exceeds the maximum seed length.", ))?; } - // On purpose not checking the SecurityStrength field of the seed, + // On purpose not checking the SecurityStrength field of the seed, // because we assume it's pure entropy and hasn't been touched by any actual algoritms yet. if security_strength > H::MAX_SECURITY_STRENGTH { return Err(KeyMaterialError::SecurityStrength( @@ -527,7 +527,7 @@ impl RNG for HashDRBG80090A { /// the hash_df function as defined in SP 800-90Ar1 section 10.3.1. /// no_of_bits_to_return is the length of the provided output buffer. -/// Because array concatenation is not available in a no_std / no_alloc build, this takes many input parameters. +/// Because array concatenation is not available in a no_std / no_alloc build, this takes many input parameters. // To leave a parameter unused, simply provide an empty array &[0u8;0] fn hash_df( in1: &[u8], @@ -550,7 +550,7 @@ fn hash_df( let len = u32::div_ceil(out.len() as u32, H::OUTPUT_LEN as u32); let mut counter: u8 = 0x01; - // note: this could probably be performance optimized a tiny bit by pulling no_of_bits_to_return.to_le_bytes() + // note: this could probably be performance optimized a tiny bit by pulling no_of_bits_to_return.to_le_bytes() // out of the loop and by merging i and counter into the same variable. for i in 1..len { let mut h = H::default(); @@ -566,7 +566,7 @@ fn hash_df( } // Handle the last block separately since not all of it will fit in the output buffer. - // TODO: Check whether it is necessary to do a last block, + // TODO: Check whether it is necessary to do a last block, // or was the requested number of bits already a multiple of the output length let bytes_written = (len - 1) as usize * H::OUTPUT_LEN; let remainder = out.len() - bytes_written; @@ -673,7 +673,7 @@ fn hashgen(v: &[u8], out: &mut [u8]) { } // Handle the last block separately since not all of it will fit in the output buffer. - // TODO: Check whether it is necessary to do a last block, + // TODO: Check whether it is necessary to do a last block, // or was the requested number of bits already a multiple of the output length let bytes_written = (m - 1) as usize * H::OUTPUT_LEN; let remainder = out.len() - bytes_written; diff --git a/crypto/rng/src/lib.rs b/crypto/rng/src/lib.rs index 1840f4c..3040358 100644 --- a/crypto/rng/src/lib.rs +++ b/crypto/rng/src/lib.rs @@ -66,13 +66,13 @@ pub type Default128BitRNG = HashDRBG_SHA256; /// The library's default RNG at the 256-bit security level. pub type Default256BitRNG = HashDRBG_SHA512; -/// Implements the five functions specified in SP 800-90A section 7.4 are -/// - instantate, -/// - generate, -/// - reseed, -/// - uninstantiate, and +/// Implements the five functions specified in SP 800-90A section 7.4 are +/// - instantate, +/// - generate, +/// - reseed, +/// - uninstantiate, and /// - health_test. -/// Note: this function implements Rust's Drop on the sensitive working state in place of the explicit +/// Note: this function implements Rust's Drop on the sensitive working state in place of the explicit /// Uninstantiate function listed in SP 800-90Ar1. pub trait Sp80090ADrbg { /// The input KeyMaterial must be of type [`KeyType::Seed`]. @@ -91,7 +91,7 @@ pub trait Sp80090ADrbg { /// required. /// """ /// - /// This function takes ownership of the seed KeyMaterial object, + /// This function takes ownership of the seed KeyMaterial object, /// to reduce the likelihood of its reuse in a second function call. /// /// There is no entropy requirement on the nonce, but it is expected as a KeyMaterial so that it @@ -106,7 +106,7 @@ pub trait Sp80090ADrbg { ) -> Result<(), RNGError>; /// Reseeds the DRBG with the provided seed. - /// TODO: this needs to be redesigned to take some sort of EntropySource object that will work well + /// TODO: this needs to be redesigned to take some sort of EntropySource object that will work well // with DRBGs that require frequent reseeding. fn reseed( &mut self, diff --git a/crypto/rng/tests/hash_drbg80090a_tests.rs b/crypto/rng/tests/hash_drbg80090a_tests.rs index 840dacf..4a8967b 100644 --- a/crypto/rng/tests/hash_drbg80090a_tests.rs +++ b/crypto/rng/tests/hash_drbg80090a_tests.rs @@ -83,11 +83,11 @@ mod tests { _ => panic!("Expected KeyMaterialError error"), } - // Skipping tests for max lengths of seeds and personalization strings - // because they are on the order of a gigabyte in size. + // Skipping tests for max lengths of seeds and personalization strings + // because they are on the order of a gigabyte in size. // Testing would blow up the test suite. - // Error case: security strength requested at init is higher than the underlying + // Error case: security strength requested at init is higher than the underlying // hash function's max security strength let mut rng = HashDRBG_SHA256::new_unititialized(); let seed = KeyMaterial256::from_bytes_as_type(&DUMMY_SEED[..32], KeyType::Seed).unwrap(); @@ -96,7 +96,7 @@ mod tests { _ => panic!("Expected KeyMaterialError error"), } - // Success case: security strength requested at init is lower than the underlying + // Success case: security strength requested at init is lower than the underlying // hash function's max security strength // ... 112 bit let mut rng = HashDRBG_SHA256::new_unititialized(); @@ -156,10 +156,9 @@ mod tests { _ => panic!("Expected KeyMaterialError error"), } - // Skipping tests for max lengths of seeds and personalization strings - // because they are on the order of a gigabyte in size. + // Skipping tests for max lengths of seeds and personalization strings + // because they are on the order of a gigabyte in size. // Testing would blow up the test suite. - } #[test] @@ -194,8 +193,8 @@ mod tests { _ => panic!("Expected Uninitialized error"), } - // Skipping tests for max lengths of seeds and personalization strings - // because they are on the order of a gigabyte in size. + // Skipping tests for max lengths of seeds and personalization strings + // because they are on the order of a gigabyte in size. // Testing would blow up the test suite. // TODO: Tests for ReseedRequired. Investigate how this gets triggered. The limits are in the exobyte range. @@ -240,8 +239,8 @@ mod tests { _ => panic!("Expected Uninitialized error"), } - // Skipping tests for max lengths of seeds and personalization strings - // because they are on the order of a gigabyte in size. + // Skipping tests for max lengths of seeds and personalization strings + // because they are on the order of a gigabyte in size. // Testing would blow up the test suite. // TODO: tests for ReseedRequired. Investigate how this gets triggered. The limits are in the exobyte range. @@ -291,8 +290,8 @@ mod tests { Ok(_) => panic!("Expected Uninitialized error"), } - // Skipping tests for max lengths of seeds and personalization strings - // because they are on the order of a gigabyte in size. + // Skipping tests for max lengths of seeds and personalization strings + // because they are on the order of a gigabyte in size. // Testing would blow up the test suite. // TODO: tests for ReseedRequired. Investigate how this gets triggered. The limits are in the exobyte range. diff --git a/crypto/sha2/src/sha256.rs b/crypto/sha2/src/sha256.rs index 7cee95c..34d0977 100644 --- a/crypto/sha2/src/sha256.rs +++ b/crypto/sha2/src/sha256.rs @@ -150,7 +150,7 @@ pub struct SHA256Internal { byte_count: u64, x_buf: Secret<[u8; 64]>, x_buf_off: usize, - // TODO: Investigate whether maximum message size (according to FIPS 180-4) should be added + // TODO: Investigate whether maximum message size (according to FIPS 180-4) should be added // (2^64 for SHA256 and 2^128 for SHA512) } diff --git a/crypto/sha2/src/sha512.rs b/crypto/sha2/src/sha512.rs index 60207eb..c31e306 100644 --- a/crypto/sha2/src/sha512.rs +++ b/crypto/sha2/src/sha512.rs @@ -161,7 +161,7 @@ pub struct SHA512Internal { _params: std::marker::PhantomData, state: Sha512State, // NOTE The code currently only supports 2^67 bits, not the full 2^128 - byte_count: u64, + byte_count: u64, x_buf: Secret<[u8; 128]>, x_buf_off: usize, } diff --git a/crypto/sha3/src/sha3.rs b/crypto/sha3/src/sha3.rs index 0393507..3da20b0 100644 --- a/crypto/sha3/src/sha3.rs +++ b/crypto/sha3/src/sha3.rs @@ -169,14 +169,14 @@ impl Hash for SHA3Internal { output } - // TODO: investigate why this doesn't take a &mut [u8; HASH_LEN] + // TODO: investigate why this doesn't take a &mut [u8; HASH_LEN] // Being able to do so would improve ergonomics fn do_final_out(mut self, output: &mut [u8]) -> usize { output.fill(0); - // this shouldn't fail because, by construction, the function is only called once, + // this shouldn't fail because, by construction, the function is only called once, // and this is the only way to absorb partial bits. - self.keccak.absorb_bits(0x02, 2).expect("do_final_out: keccak.absorb_bits failed."); + self.keccak.absorb_bits(0x02, 2).expect("do_final_out: keccak.absorb_bits failed."); let bytes_written = if output.len() <= self.output_len() { self.keccak.squeeze(output) @@ -210,7 +210,7 @@ impl Hash for SHA3Internal { ) -> Result { output.fill(0); - // Mutants note: This is just bit-setting into empty space. + // Mutants note: This is just bit-setting into empty space. // It works the same regardless of whether it's OR or XOR. let mut final_input: u16 = ((partial_byte as u16) & ((1 << num_partial_bits) - 1)) | (0x02 << num_partial_bits); diff --git a/crypto/sha3/src/shake.rs b/crypto/sha3/src/shake.rs index 1bd91dc..6ba2a88 100644 --- a/crypto/sha3/src/shake.rs +++ b/crypto/sha3/src/shake.rs @@ -92,7 +92,7 @@ impl SHAKEInternal { mut self, additional_input: &[u8], ) -> Result, KDFError> { - // At the moment, oversized KeyMaterial is returned for most cases. + // At the moment, oversized KeyMaterial is returned for most cases. let mut output_key = KeyMaterial::<64>::new(); self.derive_key_out_final_internal(additional_input, &mut output_key)?; @@ -307,7 +307,7 @@ impl XOF for SHAKEInternal { if !(1..=7).contains(&num_partial_bits) { return Err(HashError::InvalidLength("must be in the range [0,7]")); } - // Mutants note: This is just bit-setting into empty space. + // Mutants note: This is just bit-setting into empty space. // It works the same regardless of whether it's OR or XOR. let mut final_input: u16 = ((partial_byte as u16) & ((1 << num_partial_bits) - 1)) | (0x0F << num_partial_bits); diff --git a/crypto/utils/src/ct.rs b/crypto/utils/src/ct.rs index 464511a..15ad1e2 100644 --- a/crypto/utils/src/ct.rs +++ b/crypto/utils/src/ct.rs @@ -23,7 +23,9 @@ macro_rules! supported_mask_type { }; } -supported_mask_type!(i64, u64); +// i32 is registered for width symmetry with the u64/i64 pair (it gets the generic boolean +// operator impls below); it has no inherent constructors yet: add them when a consumer needs them. +supported_mask_type!(i64, u64, u32, i32); /// Helper functions for checking some condition on some data using constant-time operations. #[derive(Clone, Copy)] @@ -51,11 +53,15 @@ impl Condition { pub const fn from_bool_var(value: bool) -> Self { Self(-(value as i64)) } - /// + /// TRUE iff bit `bit` of `value` is set. The bit index must be public data. + /// The unsigned widths spell this `is_bit_set(value, bit)` too; their + /// `from_lsb(value)` is the `bit = 0` special case. pub const fn is_bit_set(value: i64, bit: i64) -> Self { Self(-((value >> bit) & 1)) } - /// + /// TRUE iff `value < 0`, i.e. the sign (top) bit is set. The unsigned + /// counterpart of this mask is `from_msb`, where the top bit carries a + /// borrow/carry instead of a sign. pub const fn is_negative(value: i64) -> Self { Self(value >> 63) } @@ -159,32 +165,103 @@ impl Condition { // then and change Hex and Base64 to use this. // (there's probably no noticeable performance difference u8 and u64 bit ops on a 64-bit machine, // but there would be on a 8, 16, or 32-bit machine.) -impl Condition { - /// TRUE is the bit vector of all 1's - pub const TRUE: Self = Self(u64::MAX); - /// FALSE is the bit vector of all 0's - pub const FALSE: Self = Self(0); +// +// The unsigned widths share one macro-generated impl so that code which is generic over its +// word size (e.g. a u64-or-u32 limb type) can be written once against identical method names. +// The i64 constructions above do NOT carry over: they rely on signed representation tricks +// (arithmetic shift for `is_negative`, the sign of `x - y` for `is_lt`) whose overflow +// reasoning is invalid for full-range unsigned values. The unsigned constructions below use +// the standard two's-complement mask identities instead, and deliberately omit ordering +// comparisons: multi-word callers derive `lt` from their subtraction borrow chain and convert +// it with `from_msb`. +macro_rules! unsigned_condition_impl { + ($($t:ty),+) => { + $( + impl Condition<$t> { + /// TRUE is the bit vector of all 1's + pub const TRUE: Self = Self(<$t>::MAX); + /// FALSE is the bit vector of all 0's + pub const FALSE: Self = Self(0); - /// this is the core logic for constant-time mask generation for unsigned integers - /// Unlike signed integers where we can rely on Two's Complement via negation `-(v as i64)`, - /// for u64 we must use wrapping subtraction to achieve the all-ones bit pattern (u64::MAX) for true - pub const fn from_bool() -> Self { - // If VALUE is true (1) -> 0 - 1 = u64::MAX (All 1s) - // If VALUE is false (0) -> 0 - 0 = 0 (All 0s) - Self(0u64.wrapping_sub(VALUE as u64)) - } - /// impl the select function manually for u64 - /// although a fully generic `impl` would be the ultimate long-term goal - pub fn select(self, a: u64, b: u64) -> u64 { - let mask = self.0; - (a & mask) | (b & !mask) - } - /// - pub fn is_true(&self) -> bool { - self.0 != 0 - } + /// Constant-time mask generation from a compile-time boolean. + /// + /// Unlike signed integers where we can rely on Two's Complement via negation + /// `-(v as i64)`, for unsigned types we must use wrapping subtraction to achieve + /// the all-ones bit pattern for true: + /// true (1) -> `0 - 1` wraps to MAX (all 1s); false (0) -> `0 - 0 = 0` (all 0s). + pub const fn from_bool() -> Self { + Self((0 as $t).wrapping_sub(VALUE as $t)) + } + /// Constant-time mask generation from a runtime boolean. + pub const fn from_bool_var(value: bool) -> Self { + Self((0 as $t).wrapping_sub(value as $t)) + } + /// Mask from the least-significant bit: TRUE iff bit 0 of `value` is set. + /// This is the parity test: `from_lsb(x)` is TRUE iff `x` is odd. It is + /// the `bit = 0` special case of [`Self::is_bit_set`]. + pub const fn from_lsb(value: $t) -> Self { + Self((0 as $t).wrapping_sub(value & 1)) + } + /// Mask from the most-significant bit: TRUE iff the top bit of `value` is set. + /// The signed counterpart of this mask is `is_negative`, where the top bit + /// carries a sign instead of a borrow/carry. + /// + /// This is the borrow/carry adaptor: the borrow word coming out of a wrapping + /// wide subtraction chain carries its meaning entirely in the top bit, so + /// `from_msb(borrow)` is the `lt` mask of that comparison with no further work. + pub const fn from_msb(value: $t) -> Self { + Self((0 as $t).wrapping_sub(value >> (<$t>::BITS - 1))) + } + /// TRUE iff bit `bit` of `value` is set. The bit index must be public data + /// (the shift amount is timing-visible on some targets). Same shape as the + /// signed `is_bit_set`; the index type follows the `u32` shift-count + /// convention of `core`. + pub const fn is_bit_set(value: $t, bit: u32) -> Self { + Self::from_lsb(value >> bit) + } + /// TRUE iff `value != 0`. + /// + /// For any nonzero `x`, `x | x.wrapping_neg()` has the top bit set (either `x` + /// or its two's complement is >= 2^(BITS-1)); for zero both sides are zero. + pub const fn is_not_zero(value: $t) -> Self { + Self::from_msb(value | value.wrapping_neg()) + } + /// TRUE iff `value == 0`. + pub const fn is_zero(value: $t) -> Self { + // Complementing the inner value maps TRUE <-> FALSE (all 1s <-> all 0s). + Self(!Self::is_not_zero(value).0) + } + /// TRUE iff `x == y`. + pub const fn is_equal(x: $t, y: $t) -> Self { + Self::is_zero(x ^ y) + } + /// Conditional selection: return `true_value` if the condition is true, otherwise + /// return `false_value`. + pub const fn select(self, true_value: $t, false_value: $t) -> $t { + (true_value & self.0) | (false_value & !self.0) + } + /// Conditionally move the source value to the destination if the condition is + /// true, otherwise nothing is moved. + pub fn mov(self, src: $t, dst: &mut $t) { + *dst = self.select(src, *dst); + } + /// Conditional swap: returns (lhs, rhs) if the condition is true, otherwise + /// returns (rhs, lhs). + pub const fn swap(self, lhs: $t, rhs: $t) -> ($t, $t) { + (self.select(rhs, lhs), self.select(lhs, rhs)) + } + /// Convert the mask to a runtime boolean. Only use this at genuine public + /// decision points: branching on the result leaks the condition's value. + pub const fn to_bool_var(self) -> bool { + self.0 != 0 + } + } + )+ + }; } +unsigned_condition_impl!(u64, u32); + impl BitAnd for Condition where MaskType: SupportedMaskType, diff --git a/crypto/utils/tests/ct_tests.rs b/crypto/utils/tests/ct_tests.rs index 8e9be76..7c4a443 100644 --- a/crypto/utils/tests/ct_tests.rs +++ b/crypto/utils/tests/ct_tests.rs @@ -124,6 +124,32 @@ mod i64_tests { assert_eq!(Condition::::is_in_list(3, &[1, 2, 3, 3, 3, 3]).to_bool_var(), true); } + #[test] + fn masks_are_canonical() { + // A mask must be exactly all-ones or all-zeros: select(-1, 0) returns the + // raw mask bits, so anything else (e.g. a 1 where -1 was intended) fails + // here even though it passes a truthiness check via to_bool_var. + fn assert_canonical(c: Condition, expected: bool) { + assert_eq!(c.select(-1, 0), if expected { -1 } else { 0 }); + } + assert_canonical(Condition::::from_bool_var(true), true); + assert_canonical(Condition::::from_bool_var(false), false); + assert_canonical(Condition::::from_bool::(), true); + assert_canonical(Condition::::from_bool::(), false); + assert_canonical(Condition::::is_bit_set(1, 0), true); + assert_canonical(Condition::::is_bit_set(8, 3), true); + assert_canonical(Condition::::is_bit_set(8, 2), false); + assert_canonical(Condition::::is_negative(-5), true); + assert_canonical(Condition::::is_negative(5), false); + assert_canonical(Condition::::is_zero(0), true); + assert_canonical(Condition::::is_zero(7), false); + assert_canonical(Condition::::is_not_zero(7), true); + assert_canonical(Condition::::is_equal(3, 3), true); + assert_canonical(Condition::::is_equal(3, 4), false); + assert_canonical(Condition::::is_lt(3, 4), true); + assert_canonical(Condition::::is_lt(4, 3), false); + } + #[test] fn test_mov() { let src = 1; @@ -216,15 +242,15 @@ mod u64_tests { #[test] fn const_tests() { // Ensure TRUE/FALSE are correctly interpreted as boolean. - assert_eq!(Condition::::TRUE.is_true(), true); - assert_eq!(Condition::::FALSE.is_true(), false); + assert_eq!(Condition::::TRUE.to_bool_var(), true); + assert_eq!(Condition::::FALSE.to_bool_var(), false); } #[test] fn from_bool() { // Compile-time const generics check - assert_eq!(Condition::::from_bool::().is_true(), true); - assert_eq!(Condition::::from_bool::().is_true(), false); + assert_eq!(Condition::::from_bool::().to_bool_var(), true); + assert_eq!(Condition::::from_bool::().to_bool_var(), false); } #[test] @@ -252,22 +278,22 @@ mod u64_tests { let f = Condition::::FALSE; // NOT - assert_eq!((!t).is_true(), false); - assert_eq!((!f).is_true(), true); + assert_eq!((!t).to_bool_var(), false); + assert_eq!((!f).to_bool_var(), true); // AND - assert_eq!((t & t).is_true(), true); - assert_eq!((t & f).is_true(), false); - assert_eq!((f & f).is_true(), false); + assert_eq!((t & t).to_bool_var(), true); + assert_eq!((t & f).to_bool_var(), false); + assert_eq!((f & f).to_bool_var(), false); // OR - assert_eq!((t | t).is_true(), true); - assert_eq!((t | f).is_true(), true); - assert_eq!((f | f).is_true(), false); + assert_eq!((t | t).to_bool_var(), true); + assert_eq!((t | f).to_bool_var(), true); + assert_eq!((f | f).to_bool_var(), false); // XOR - assert_eq!((t ^ t).is_true(), false); - assert_eq!((t ^ f).is_true(), true); + assert_eq!((t ^ t).to_bool_var(), false); + assert_eq!((t ^ f).to_bool_var(), true); } } @@ -364,8 +390,183 @@ mod generic_impl_tests { } } +/// One test module per unsigned width, generated from the same macro so u64 and u32 stay at +/// exact behavioural parity. Boundary set: 0, 1, MAX, MAX-1, 1 << (BITS-1) โ€” the values where +/// signed-representation tricks would produce wrong masks if they had leaked into the +/// unsigned constructions. +macro_rules! unsigned_condition_tests { + ($mod_name:ident, $t:ty, $wide:ty) => { + #[cfg(test)] + mod $mod_name { + use super::*; + + const MSB: $t = 1 << (<$t>::BITS - 1); + const BOUNDARY: [$t; 5] = [0, 1, <$t>::MAX, <$t>::MAX - 1, MSB]; + + /// A mask must be exactly all-ones or all-zeros; anything else (e.g. a `1` where + /// MAX was intended) silently corrupts every select it feeds. `select(MAX, 0)` + /// returns the raw mask bits, so this asserts canonicality through the public API. + fn assert_canonical(cond: Condition<$t>, expected: bool) { + let raw = cond.select(<$t>::MAX, 0); + assert_eq!(raw, if expected { <$t>::MAX } else { 0 }); + assert_eq!(cond.to_bool_var(), expected); + } + + #[test] + fn consts() { + assert_canonical(Condition::<$t>::TRUE, true); + assert_canonical(Condition::<$t>::FALSE, false); + } + + #[test] + fn from_bool() { + assert_canonical(Condition::<$t>::from_bool::(), true); + assert_canonical(Condition::<$t>::from_bool::(), false); + assert_canonical(Condition::<$t>::from_bool_var(true), true); + assert_canonical(Condition::<$t>::from_bool_var(false), false); + } + + #[test] + fn from_lsb() { + for v in BOUNDARY { + assert_canonical(Condition::<$t>::from_lsb(v), v & 1 == 1); + } + } + + #[test] + fn from_msb() { + for v in BOUNDARY { + assert_canonical(Condition::<$t>::from_msb(v), v >> (<$t>::BITS - 1) == 1); + } + } + + /// `from_msb` exists to convert the borrow word of a wrapping subtraction chain + /// into a mask: `(x - y) >> BITS` of the widening subtraction is all-ones in the + /// low word iff x < y. + #[test] + fn from_msb_as_borrow_adaptor() { + for x in BOUNDARY { + for y in BOUNDARY { + let borrow = ((x as $wide).wrapping_sub(y as $wide) >> <$t>::BITS) as $t; + assert_canonical(Condition::<$t>::from_msb(borrow), x < y); + } + } + } + + #[test] + fn is_bit_set() { + // bit 0 agrees with from_lsb on every boundary value + for v in BOUNDARY { + assert_eq!( + Condition::<$t>::is_bit_set(v, 0).to_bool_var(), + Condition::<$t>::from_lsb(v).to_bool_var() + ); + } + // each single-bit value reports exactly its own bit + for k in 0..<$t>::BITS { + let v: $t = 1 << k; + for bit in 0..<$t>::BITS { + assert_canonical(Condition::<$t>::is_bit_set(v, bit), bit == k); + } + } + // the top bit agrees with from_msb + for v in BOUNDARY { + assert_eq!( + Condition::<$t>::is_bit_set(v, <$t>::BITS - 1).to_bool_var(), + Condition::<$t>::from_msb(v).to_bool_var() + ); + } + } + + #[test] + fn is_zero_is_not_zero() { + for v in BOUNDARY { + assert_canonical(Condition::<$t>::is_zero(v), v == 0); + assert_canonical(Condition::<$t>::is_not_zero(v), v != 0); + } + } + + #[test] + fn is_equal() { + for x in BOUNDARY { + for y in BOUNDARY { + assert_canonical(Condition::<$t>::is_equal(x, y), x == y); + } + } + } + + #[test] + fn select_preserves_all_bits() { + // Patterned values (not just MAX/0) so a mask with any wrong bit shows up. + let a: $t = (0xDEADBEEFCAFEBABE_u64 & <$t>::MAX as u64) as $t; + let b: $t = (0x0123456789ABCDEF_u64 & <$t>::MAX as u64) as $t; + assert_eq!(Condition::<$t>::TRUE.select(a, b), a); + assert_eq!(Condition::<$t>::FALSE.select(a, b), b); + } + + #[test] + fn mov() { + let src: $t = 1; + let mut dst: $t = 2; + Condition::<$t>::from_bool::().mov(src, &mut dst); + assert_eq!(dst, 1); + dst = 2; + Condition::<$t>::from_bool::().mov(src, &mut dst); + assert_eq!(dst, 2); + } + + #[test] + fn swap() { + let (lhs, rhs) = Condition::<$t>::from_bool::().swap(1, 2); + assert_eq!((lhs, rhs), (2, 1)); + let (lhs, rhs) = Condition::<$t>::from_bool::().swap(1, 2); + assert_eq!((lhs, rhs), (1, 2)); + } + + #[test] + fn boolean_operators() { + let t = Condition::<$t>::TRUE; + let f = Condition::<$t>::FALSE; + assert_canonical(!t, false); + assert_canonical(!f, true); + assert_canonical(t & f, false); + assert_canonical(t & t, true); + assert_canonical(t | f, true); + assert_canonical(f | f, false); + assert_canonical(t ^ t, false); + assert_canonical(t ^ f, true); + } + } + }; +} + +unsigned_condition_tests!(unsigned_u64_tests, u64, u128); +unsigned_condition_tests!(unsigned_u32_tests, u32, u64); + #[cfg(test)] mod ct_bytes_tests { + #[test] + fn test_ct_eq_zero_bytes() { + use bouncycastle_utils::ct::ct_eq_zero_bytes; + + // all-zero inputs, including the empty slice + assert!(ct_eq_zero_bytes(&[])); + assert!(ct_eq_zero_bytes(&[0])); + assert!(ct_eq_zero_bytes(&[0; 32])); + + // a nonzero byte anywhere must be detected: first, last, and high-bit-only + assert!(!ct_eq_zero_bytes(&[1])); + let mut buf = [0u8; 32]; + buf[0] = 1; + assert!(!ct_eq_zero_bytes(&buf)); + buf[0] = 0; + buf[31] = 1; + assert!(!ct_eq_zero_bytes(&buf)); + buf[31] = 0; + buf[15] = 0x80; + assert!(!ct_eq_zero_bytes(&buf)); + } + #[test] fn test_conditional_copy_bytes() { use bouncycastle_utils::ct::conditional_copy_bytes;