pub struct Secret<T>(T);
impl<T> Secret<T> {
pub fn map<U>(&self, func: impl FnOnce(&T) -> U) -> Secret<U> {
Secret(func(&self.0))
}
}
impl<T: AsRef<[u8]>> PartialEq<&[u8]> for Secret<T> {
fn eq(&self, other: &&[u8]) -> bool {
constant_time_eq(self.0.as_ref(), other)
}
}
/* Some other file */
use secret::Secret;
// Translated from the example
fn check_mac<T: AsRef<[u8]>>(mac_secret: Secret<T>, message: &[u8], mac: &[u8]) -> bool {
// This returns a new Secret<[u8; 32]>
let computed_mac = mac_secret.map(|secret| hmac_sha_256(secret.as_ref(), message));
// This uses the `constant_time_eq` impl from above
computed_mac == mac
}
I think the interesting part of the example is what you _can't_ do in the other file. It's pretty hard to misuse because the return type of `Secret::map` is a new `Secret`, the only way to do `==` on a `Secret<T>` uses a constant time compare.I guess my main point is that when you have a instead of having to add new things at the language _level_, if I have something as powerful as the rust type system I can implement the same functionality in not much of code.