diff --git a/src/ibe/waters.rs b/src/ibe/waters.rs index 83cf1c8..fe2294c 100644 --- a/src/ibe/waters.rs +++ b/src/ibe/waters.rs @@ -367,5 +367,31 @@ impl Compress for CipherText { #[cfg(test)] mod tests { + use super::*; + test_ibe!(Waters); + + // Two distinct strings whose SHA3-256 digests happen to agree on the 8 bits + // that the buggy `bits()` read (one bit each from the last 8 bytes of the + // digest). Under the old code, both produce the same curve point — i.e. a + // user secret key extracted for one would decrypt ciphertexts for the other. + #[test] + fn realistic_identities_do_not_collide_in_entangle() { + use crate::Derive; + + let mut rng = rand::thread_rng(); + let (pk, _sk) = Waters::setup(&mut rng); + + let id_a = Identity::derive_str("user17@example.com"); + let id_b = Identity::derive_str("user20@example.com"); + assert_ne!(id_a.0, id_b.0, "sanity: digests must differ"); + + let u_a = G1Affine::from(entangle(&pk, &id_a)); + let u_b = G1Affine::from(entangle(&pk, &id_b)); + + assert_ne!( + u_a, u_b, + "distinct identities must entangle to distinct curve points" + ); + } } diff --git a/src/kem/kiltz_vahlis_one.rs b/src/kem/kiltz_vahlis_one.rs index 5e1af1c..401fb1e 100644 --- a/src/kem/kiltz_vahlis_one.rs +++ b/src/kem/kiltz_vahlis_one.rs @@ -351,4 +351,26 @@ mod tests { #[cfg(feature = "mkem")] test_multi_kem!(KV1); + + // Two distinct strings whose SHA3-512 digests happen to agree on the 8 bits + // that the buggy `bits()` read (one bit each from the last 8 bytes of the + // digest). Under the old code, both produce the same curve point — i.e. a + // user secret key extracted for one would decrypt ciphertexts for the other. + #[test] + fn realistic_identities_do_not_collide_in_hash_to_curve() { + let mut rng = rand::thread_rng(); + let (pk, _sk) = KV1::setup(&mut rng); + + let id_a = Identity::derive_str("user12@example.com"); + let id_b = Identity::derive_str("user26@example.com"); + assert_ne!(id_a.0, id_b.0, "sanity: digests must differ"); + + let h_a = G1Affine::from(hash_to_curve(&pk, &id_a)); + let h_b = G1Affine::from(hash_to_curve(&pk, &id_b)); + + assert_ne!( + h_a, h_b, + "distinct identities must hash to distinct curve points" + ); + } } diff --git a/src/util.rs b/src/util.rs index 0aae16f..0f315f4 100644 --- a/src/util.rs +++ b/src/util.rs @@ -41,11 +41,40 @@ pub fn rand_gt(rng: &mut R) -> Gt { } pub fn bits<'a>(slice: &'a [u8]) -> impl Iterator + 'a { - slice - .iter() - .rev() - .zip((0..8).rev()) - .map(|(x, i)| subtle::Choice::from((*x >> i) & 1)) + slice.iter().rev().flat_map(|byte| { + (0..8u8) + .rev() + .map(move |i| subtle::Choice::from((byte >> i) & 1)) + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn bits_produces_eight_bits_per_byte() { + let data = [0xABu8; 64]; + assert_eq!(bits(&data).count(), 64 * 8); + + let data = [0xCDu8; 32]; + assert_eq!(bits(&data).count(), 32 * 8); + + let data = [0u8; 0]; + assert_eq!(bits(&data).count(), 0); + + let data = [0xFFu8; 1]; + let bits_vec: std::vec::Vec = bits(&data).map(|c| c.unwrap_u8()).collect(); + assert_eq!(bits_vec, [1, 1, 1, 1, 1, 1, 1, 1]); + } + + #[test] + fn bits_reflects_exact_bit_pattern() { + // 0b1010_0101 = 0xA5, high bit (bit 7) first. + let data = [0xA5u8]; + let bits_vec: std::vec::Vec = bits(&data).map(|c| c.unwrap_u8()).collect(); + assert_eq!(bits_vec, [1, 0, 1, 0, 0, 1, 0, 1]); + } } pub fn sha3_256(slice: &[u8]) -> [u8; 32] {