This Rust example seems like it misses the point? Rune detects that you're working with data that is marked as secret, and gives you a compiler guarantee that it's defending against some set of known attacks.
The Rust code above depends on the programmer to consistently remember to enforce safety, and to do so correctly every time.
Sure, you could probably implement that as a library. But, "We don't see much value in compiler help with this, a combination of libraries and being careful gets the job done," would be a peculiar position for a rustacean to defend.
Great callout, I haven't had my coffee yet. Here is a version that better shows what I intended
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.
That covers the one case in the example, but the language goes even further than that in ensuring constant-time processing of secrets, including ensuring speculative execution in the CPU won't expose the data to timing attacks.
I don't know enough about the subject to really evaluate this in detail, but I am more than willing to at least entertain the notion that the problem space is thorny enough that a language-level solution really can do some things that can't be as effectively accomplished with a library solution. Even in a language with a strong compiler like Rust.
It all gets more complicated when you want to pass more than one secret parameter, or the function already returns a Secret - now you need a monad. The key feature seems to be that the code does not need 'map' or anything, the secrecy flag is propagated regardless.
The Rust code above depends on the programmer to consistently remember to enforce safety, and to do so correctly every time.
Sure, you could probably implement that as a library. But, "We don't see much value in compiler help with this, a combination of libraries and being careful gets the job done," would be a peculiar position for a rustacean to defend.