use alloc::vec::Vec;
use subtle::ConstantTimeEq;
use zeroize::{Zeroize, ZeroizeOnDrop};
#[derive(Clone, Zeroize, ZeroizeOnDrop)]
pub struct SharedSecret {
bytes: Vec<u8>,
}
impl SharedSecret {
pub fn new(bytes: Vec<u8>) -> Self {
Self { bytes }
}
pub fn as_bytes(&self) -> &[u8] {
&self.bytes
}
}
impl ConstantTimeEq for SharedSecret {
fn ct_eq(&self, other: &Self) -> subtle::Choice {
self.bytes.ct_eq(&other.bytes)
}
}
impl PartialEq for SharedSecret {
fn eq(&self, other: &Self) -> bool {
crate::ct::declassify_choice(self.ct_eq(other))
}
}
impl Eq for SharedSecret {}
impl core::fmt::Debug for SharedSecret {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("SharedSecret")
.field("bytes", &"[REDACTED]")
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
#[test]
fn zeroize_clears_all_secret_fields() {
let mut secret = SharedSecret::new(vec![0xAA; 32]);
secret.zeroize();
assert!(secret.bytes.is_empty());
}
#[test]
fn equality_distinguishes_secrets() {
let a = SharedSecret::new(vec![0xAA; 32]);
let b = SharedSecret::new(vec![0xAA; 32]);
let c = SharedSecret::new(vec![0xBB; 32]);
let short = SharedSecret::new(vec![0xAA; 16]);
assert_eq!(a, b);
assert_ne!(a, c);
assert_ne!(a, short);
}
#[test]
fn debug_redacts_secret_bytes() {
use alloc::format;
let rendered = format!("{:?}", SharedSecret::new(vec![0xAB; 4]));
assert_eq!(rendered, "SharedSecret { bytes: \"[REDACTED]\" }");
}
}