| // Copyright 2026 The Fuchsia Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| use aes::cipher::{BlockCipherEncrypt, KeyInit}; | |
| /// Set Identity Hash (SIH) function as defined in CSIS 1.1 Specification, | |
| /// Section 4.7. `sih(k, r) = e(k, r') mod 2^24` | |
| /// where `r' = padding || r`, padding is 104 bits of 0. | |
| /// The LSO of r becomes the LSO of r'. | |
| pub fn set_identity_hash(key: &[u8; 16], r: &[u8; 3]) -> [u8; 3] { | |
| let mut plaintext = [0u8; 16]; | |
| // r' = padding || r. LSO of r is LSO of r'. | |
| // Since Bluetooth is little endian, LSO is at index 0. | |
| plaintext[0] = r[0]; | |
| plaintext[1] = r[1]; | |
| plaintext[2] = r[2]; | |
| // The rest of the bytes are already 0 (padding). | |
| // Bluetooth `e` function uses AES-128 but is LSB-first (little endian). | |
| // The `aes` crate is MSB-first. We need to reverse the input and output. | |
| let mut key_reversed = *key; | |
| key_reversed.reverse(); | |
| plaintext.reverse(); | |
| let key_block = key_reversed.into(); | |
| let mut block = plaintext.into(); | |
| let cipher = aes::Aes128::new(&key_block); | |
| cipher.encrypt_block(&mut block); | |
| // sih keeps the least significant 24 bits of e, the last three octets of | |
| // the MSB-first block, returned LSO first. | |
| [block[15], block[14], block[13]] | |
| } | |
| /// Sample values from the CSIS v1.1 specification, least significant octet | |
| /// first. | |
| #[cfg(test)] | |
| pub(crate) mod test_vectors { | |
| /// Appendix A.1, `k`. | |
| pub(crate) const SIRK: [u8; 16] = [ | |
| 0xcd, 0xcc, 0x72, 0xdd, 0x86, 0x8c, 0xcd, 0xce, 0x22, 0xfd, 0xa1, 0x21, 0x09, 0x7d, 0x7d, | |
| 0x45, | |
| ]; | |
| /// Appendix A.1, `r`. In MSB-first notation, 0x69f563. | |
| pub(crate) const PRAND: [u8; 3] = [0x63, 0xf5, 0x69]; | |
| /// Appendix A.1, `sih(k, r)`. In MSB-first notation, 0x1948da. | |
| pub(crate) const HASH: [u8; 3] = [0xda, 0x48, 0x19]; | |
| /// The RSI that a Set Member holding `SIRK` advertises for `PRAND`, which | |
| /// is `hash || prand`. | |
| pub(crate) const RSI: [u8; 6] = [HASH[0], HASH[1], HASH[2], PRAND[0], PRAND[1], PRAND[2]]; | |
| } | |
| #[cfg(test)] | |
| mod tests { | |
| use super::test_vectors::{HASH, PRAND, SIRK}; | |
| use super::*; | |
| #[test] | |
| fn set_identity_hash_matches_the_spec_vector() { | |
| assert_eq!(set_identity_hash(&SIRK, &PRAND), HASH); | |
| } | |
| } |