4 ms·
~I love when I see programming languages who's first advertised features are implementable in 8 lines of rust~ Edit: ^ the above had the wrong tone. Thanks to
by grantjpowell 4y ago
~I love when I see programming languages who's first advertised features are implementable in 8 lines of rust~
Edit: ^ the above had the wrong tone. Thanks to dang for pointing it out. What I meant to express was that it's possible to accomplish a similar safety/ergonomics at the library level in rust in not too many SLOC. My personal preference is towards Rust's approach because the type system gives really powerful composable primitives which makes it possible to have the compiler check a wide range of invariants, instead of just the ones that are common/special enough to go into the language itself
(Example edited after comments from mumblemumble)
// The struct is public, but the contents are private, meaning you can't directly access the secret once it's inside the struct
pub struct Secret<T>(T);
impl<T> Secret<T> {
// The only public way to access the secret, returns a new secret
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> {
// == does the correct thing (and only works for types that would make sense (`AsRef<[u8]>`)
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
}
Edit: It looks like you can implment SOA as a macro too https://github.com/lumol-org/soa-derive https://github.com/lumol-org/soa-derive
Edit: mumblemumble helpfully points out I demonstrated this poorly, so I tried to better demostrate what I was going for in this comment https://news.ycombinator.com/item?id=33764037 https://news.ycombinator.com/item?id=33764037
- deleted 4y ago[deleted]
- mumblemumble 4y agoThis 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.
- grantjpowell 4y agoGreat 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.
- yccs27 4y agoIt 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.
- grantjpowell 4y ago> now you need a Monad "need a Monad" sounds scary but in practice it looks like this impl<T> Secret<T> { pub fn map<U>(&self, func: impl FnOnce(&T) -> U) -> Secret<U> { Secret(func(&self.0)) } pub fn flat_map<U>(&self, func: impl FnOnce(&T) -> Secret<U>) -> Secret<U> { func(&self.0) } } If you need an escape hatch for something more complicated, you could provide an api to that impl<T> Secret<T> { pub unsafe fn reveal(&self) -> &T { &self.0 } }
- 3a2d29 4y agoCompiler checking is always better than programmer checking. As someone who uses rust, I assume you would prefer the former absolutely.
- dang 4y agoPlease don't be snarky when evaluating someone else's work. Your comment would be fine without the opening swipe. https://news.ycombinator.com/newsguidelines.html https://news.ycombinator.com/newsguidelines.html