1use alloc::vec::Vec;
4use subtle::ConstantTimeEq;
5use zeroize::{Zeroize, ZeroizeOnDrop};
6
7#[derive(Clone, Zeroize, ZeroizeOnDrop)]
18pub struct SharedSecret {
19 bytes: Vec<u8>,
20}
21
22impl SharedSecret {
23 pub fn new(bytes: Vec<u8>) -> Self {
25 Self { bytes }
26 }
27
28 pub fn as_bytes(&self) -> &[u8] {
30 &self.bytes
31 }
32}
33
34impl ConstantTimeEq for SharedSecret {
35 fn ct_eq(&self, other: &Self) -> subtle::Choice {
36 self.bytes.ct_eq(&other.bytes)
37 }
38}
39
40impl PartialEq for SharedSecret {
41 fn eq(&self, other: &Self) -> bool {
42 crate::ct::declassify_choice(self.ct_eq(other))
45 }
46}
47
48impl Eq for SharedSecret {}
49
50impl core::fmt::Debug for SharedSecret {
51 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
52 f.debug_struct("SharedSecret")
53 .field("bytes", &"[REDACTED]")
54 .finish()
55 }
56}
57
58#[cfg(test)]
59mod tests {
60 use super::*;
61 use alloc::vec;
62
63 #[test]
66 fn zeroize_clears_all_secret_fields() {
67 let mut secret = SharedSecret::new(vec![0xAA; 32]);
68 secret.zeroize();
69 assert!(secret.bytes.is_empty());
70 }
71
72 #[test]
76 fn equality_distinguishes_secrets() {
77 let a = SharedSecret::new(vec![0xAA; 32]);
78 let b = SharedSecret::new(vec![0xAA; 32]);
79 let c = SharedSecret::new(vec![0xBB; 32]);
80 let short = SharedSecret::new(vec![0xAA; 16]);
81 assert_eq!(a, b);
82 assert_ne!(a, c);
83 assert_ne!(a, short);
84 }
85
86 #[test]
89 fn debug_redacts_secret_bytes() {
90 use alloc::format;
91 let rendered = format!("{:?}", SharedSecret::new(vec![0xAB; 4]));
92 assert_eq!(rendered, "SharedSecret { bytes: \"[REDACTED]\" }");
93 }
94}