Skip to main content

nectar_primitives/chunk/encryption/
key.rs

1//! Encryption key type.
2
3use std::mem::size_of;
4
5use alloy_primitives::B256;
6use subtle::ConstantTimeEq;
7use zeroize::{Zeroize, ZeroizeOnDrop};
8
9use super::error::EncryptionError;
10
11/// 32-byte encryption key for chunk encryption.
12///
13/// Key material is zeroed on drop via `zeroize`. `Copy` is intentionally not
14/// implemented to prevent implicit unzeroed copies on the stack.
15/// Equality is constant-time via `subtle::ConstantTimeEq`.
16#[derive(Clone, Zeroize, ZeroizeOnDrop)]
17pub struct EncryptionKey([u8; size_of::<B256>()]);
18
19impl ConstantTimeEq for EncryptionKey {
20    fn ct_eq(&self, other: &Self) -> subtle::Choice {
21        self.0.ct_eq(&other.0)
22    }
23}
24
25impl PartialEq for EncryptionKey {
26    fn eq(&self, other: &Self) -> bool {
27        self.ct_eq(other).into()
28    }
29}
30
31impl Eq for EncryptionKey {}
32
33impl EncryptionKey {
34    /// Byte length of an encryption key.
35    pub const SIZE: usize = size_of::<B256>();
36
37    /// Access the raw key bytes.
38    pub const fn as_bytes(&self) -> &[u8; Self::SIZE] {
39        &self.0
40    }
41
42    /// Generate a random encryption key.
43    #[cfg(feature = "encryption")]
44    pub fn generate() -> Self {
45        use rand::RngExt;
46        Self(rand::rng().random())
47    }
48}
49
50impl From<[u8; Self::SIZE]> for EncryptionKey {
51    fn from(bytes: [u8; Self::SIZE]) -> Self {
52        Self(bytes)
53    }
54}
55
56impl From<B256> for EncryptionKey {
57    fn from(b: B256) -> Self {
58        Self(b.0)
59    }
60}
61
62impl AsRef<[u8; Self::SIZE]> for EncryptionKey {
63    fn as_ref(&self) -> &[u8; Self::SIZE] {
64        &self.0
65    }
66}
67
68impl AsRef<[u8]> for EncryptionKey {
69    fn as_ref(&self) -> &[u8] {
70        &self.0
71    }
72}
73
74impl TryFrom<&[u8]> for EncryptionKey {
75    type Error = EncryptionError;
76
77    fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
78        if slice.len() != Self::SIZE {
79            return Err(EncryptionError::InvalidKeyLength { len: slice.len() });
80        }
81        let mut bytes = [0u8; Self::SIZE];
82        bytes.copy_from_slice(slice);
83        Ok(Self(bytes))
84    }
85}
86
87impl std::fmt::Debug for EncryptionKey {
88    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89        // Show first 4 bytes as hex for identification
90        write!(
91            f,
92            "EncryptionKey({:02x}{:02x}{:02x}{:02x}..)",
93            self.0[0], self.0[1], self.0[2], self.0[3]
94        )
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    #[test]
103    fn from_bytes_roundtrip() {
104        let bytes = [42u8; EncryptionKey::SIZE];
105        let key = EncryptionKey::from(bytes);
106        assert_eq!(key.as_bytes(), &bytes);
107    }
108
109    #[test]
110    fn from_b256() {
111        let b = B256::repeat_byte(0xab);
112        let key = EncryptionKey::from(b);
113        assert_eq!(
114            <EncryptionKey as AsRef<[u8; EncryptionKey::SIZE]>>::as_ref(&key),
115            &[0xab; EncryptionKey::SIZE]
116        );
117    }
118
119    #[test]
120    fn try_from_slice_valid() {
121        let slice = [7u8; EncryptionKey::SIZE];
122        let key = EncryptionKey::try_from(slice.as_slice()).unwrap();
123        assert_eq!(
124            <EncryptionKey as AsRef<[u8; EncryptionKey::SIZE]>>::as_ref(&key),
125            &slice
126        );
127    }
128
129    #[test]
130    fn try_from_slice_invalid() {
131        let short = [0u8; 16];
132        let err = EncryptionKey::try_from(short.as_slice()).unwrap_err();
133        assert!(matches!(err, EncryptionError::InvalidKeyLength { len: 16 }));
134    }
135
136    #[test]
137    fn debug_shows_hex_prefix() {
138        let key = EncryptionKey::from([
139            0xab, 0xcd, 0xef, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
140            0, 0, 0, 0, 0, 0, 0,
141        ]);
142        let dbg = format!("{:?}", key);
143        assert!(dbg.contains("abcdef01"));
144    }
145
146    #[cfg(feature = "encryption")]
147    #[test]
148    fn generate_produces_key() {
149        let k1 = EncryptionKey::generate();
150        let k2 = EncryptionKey::generate();
151        // Extremely unlikely to collide
152        assert_ne!(k1, k2);
153    }
154
155    #[test]
156    fn constant_time_equality() {
157        let k1 = EncryptionKey::from([0x42; EncryptionKey::SIZE]);
158        let k2 = EncryptionKey::from([0x42; EncryptionKey::SIZE]);
159        let k3 = EncryptionKey::from([0x43; EncryptionKey::SIZE]);
160        assert_eq!(k1, k2);
161        assert_ne!(k1, k3);
162    }
163}