1use hmac::{
2 Hmac, Mac, NewMac,
3 digest::{
4 Update, BlockInput, FixedOutput, Reset,
5 generic_array::ArrayLength,
6 },
7};
8use crate::error::Error;
9
10pub fn hotp<D>(key: &[u8], counter: u64, digits: u32) -> Result<u64, Error>
16where D: Update + BlockInput + FixedOutput + Reset + Default + Clone,
17 D::BlockSize: ArrayLength<u8>,
18{
19 if digits < 6 {
20 return Err(Error::InvalidDigitsSize);
21 }
22
23 let data = counter.to_be_bytes();
24
25 let mut hmac = Hmac::<D>::new_from_slice(key).map_err(Error::InvalidKeySize)?;
26 hmac.update(&data);
27
28 let result = hmac.finalize().into_bytes();
29
30 let offset = usize::from(result[result.len() - 1] & 0xF);
31 let binary = (u64::from(result[offset] & 0x7F) << 24)
32 | (u64::from(result[offset + 1]) << 16)
33 | (u64::from(result[offset + 2]) << 8)
34 | u64::from(result[offset + 3]);
35
36 Ok(binary % 10u64.pow(digits))
37}