//! Acceptance tests for ADR-120 §2.3 ↔ ADR-118 §2.1 wiring — `SignatureHasher` //! derives `rf_signature_hash` end-to-end through `BfldEmitter`. #![cfg(feature = "std")] use wifi_densepose_bfld::{ BfldEmitter, IdentityEmbedding, SensingInputs, SignatureHasher, EMBEDDING_DIM, SITE_SALT_LEN, }; fn salt(seed: u8) -> [u8; SITE_SALT_LEN] { let mut s = [0u8; SITE_SALT_LEN]; for (i, b) in s.iter_mut().enumerate() { *b = seed.wrapping_add(i as u8); } s } fn embedding(seed: u8) -> IdentityEmbedding { let mut a = [0.0f32; EMBEDDING_DIM]; for (i, v) in a.iter_mut().enumerate() { *v = (i as f32 + seed as f32) * 0.001; } IdentityEmbedding::from_raw(a) } fn inputs(seed: u8) -> SensingInputs { SensingInputs { timestamp_ns: 1_700_000_000_000_000_000 + (seed as u64) * 1_000_000_000, presence: true, motion: 0.5, person_count: 1, sensing_confidence: 0.9, sep: 0.2, stab: 0.2, consist: 0.2, risk_conf: 0.2, rf_signature_hash: Some([0xFF; 32]), // caller-supplied "wrong" hash } } #[test] fn no_hasher_passes_caller_supplied_hash_through() { let mut e = BfldEmitter::new("seed-01"); let out = e.emit(inputs(0), Some(embedding(0))).unwrap(); assert_eq!(out.rf_signature_hash, Some([0xFF; 32])); } #[test] fn installed_hasher_overrides_caller_supplied_hash() { let mut e = BfldEmitter::new("seed-01").with_signature_hasher(SignatureHasher::new(salt(7))); let out = e.emit(inputs(0), Some(embedding(0))).unwrap(); let hash = out.rf_signature_hash.unwrap(); assert_ne!(hash, [0xFF; 32], "derived hash must override caller-supplied"); assert_ne!(hash, [0x00; 32], "derived hash must be non-trivial"); } #[test] fn same_emitter_same_inputs_produce_same_hash() { let mut e_a = BfldEmitter::new("seed-01").with_signature_hasher(SignatureHasher::new(salt(7))); let mut e_b = BfldEmitter::new("seed-01").with_signature_hasher(SignatureHasher::new(salt(7))); let a = e_a.emit(inputs(0), Some(embedding(0))).unwrap(); let b = e_b.emit(inputs(0), Some(embedding(0))).unwrap(); assert_eq!(a.rf_signature_hash, b.rf_signature_hash); } #[test] fn different_site_salts_produce_different_hashes_end_to_end() { let mut e_a = BfldEmitter::new("seed-01").with_signature_hasher(SignatureHasher::new(salt(1))); let mut e_b = BfldEmitter::new("seed-02").with_signature_hasher(SignatureHasher::new(salt(2))); // Same embedding, same inputs → different sites must produce different hashes. let a = e_a.emit(inputs(0), Some(embedding(0))).unwrap(); let b = e_b.emit(inputs(0), Some(embedding(0))).unwrap(); assert_ne!( a.rf_signature_hash, b.rf_signature_hash, "cross-site emit must produce uncorrelated hashes", ); } #[test] fn no_embedding_falls_back_to_risk_factor_bytes() { let mut e = BfldEmitter::new("seed-01").with_signature_hasher(SignatureHasher::new(salt(5))); let out = e.emit(inputs(0), None).unwrap(); let hash = out.rf_signature_hash.unwrap(); assert_ne!(hash, [0xFF; 32]); // still derived (fallback path), not caller-supplied } #[test] fn fallback_hash_differs_from_embedding_hash() { let mut e_with = BfldEmitter::new("seed-01").with_signature_hasher(SignatureHasher::new(salt(9))); let mut e_without = BfldEmitter::new("seed-01").with_signature_hasher(SignatureHasher::new(salt(9))); let with_emb = e_with.emit(inputs(0), Some(embedding(0))).unwrap(); let no_emb = e_without.emit(inputs(0), None).unwrap(); assert_ne!( with_emb.rf_signature_hash, no_emb.rf_signature_hash, "embedding bytes and risk-factor bytes should hash to different values", ); }