use super::{CodecError, Id32};
use minicbor::Encoder;
use sha2::{Digest, Sha256};
pub(crate) fn domain_hash(
domain: &'static str,
ids: &[Id32],
index: Option<u64>,
tail: Option<&[u8]>,
) -> Result<Id32, CodecError> {
if domain.len() > 14 || ids.len() > 3 {
return Err(CodecError::Malformed);
}
let mut prefix = [0; 128];
let mut encoder = Encoder::new(&mut prefix[..]);
encoder
.array(1 + ids.len() as u64 + u64::from(index.is_some()) + u64::from(tail.is_some()))
.map_err(|_| CodecError::Malformed)?;
encoder.str(domain).map_err(|_| CodecError::Malformed)?;
for id in ids {
encoder.bytes(id).map_err(|_| CodecError::Malformed)?;
}
if let Some(index) = index {
encoder.u64(index).map_err(|_| CodecError::Malformed)?;
}
let used = 128 - encoder.writer().len();
let mut hash = Sha256::new();
hash.update(&prefix[..used]);
if let Some(record) = tail {
hash.update(record);
}
Ok(hash.finalize().into())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fixed_prefix_capacity_handles_its_maximum_and_rejects_widened_contracts() {
let ids = [[0xff; 32]; 3];
assert!(domain_hash("raiv/episode/1", &ids, Some(u64::MAX), Some(&[0xf6])).is_ok());
assert_eq!(
domain_hash("raiv/episode/10", &ids, None, None),
Err(CodecError::Malformed)
);
assert_eq!(
domain_hash("raiv/episode/1", &[[0; 32]; 4], None, None),
Err(CodecError::Malformed)
);
}
}