use serde::{Deserialize, Serialize};
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(default)]
pub struct EncryptionConfig {
#[serde(default)]
pub private_key: String,
}
impl std::fmt::Debug for EncryptionConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("EncryptionConfig")
.field("private_key", &"[REDACTED]")
.finish()
}
}
impl EncryptionConfig {
pub fn private_key_bytes(&self) -> Option<[u8; 32]> {
let key_hex = self.private_key.strip_prefix("0x").unwrap_or(&self.private_key);
if key_hex.is_empty() {
return None;
}
let bytes = match hex::decode(key_hex) {
Ok(b) => b,
Err(e) => {
tracing::warn!(
hex_len = key_hex.len(),
"encryption private key is not valid hex: {e}. \
Key will be treated as unconfigured — HPKE decryption unavailable."
);
return None;
}
};
if bytes.len() != 32 {
tracing::warn!(
actual_len = bytes.len(),
expected_len = 32,
"encryption private key has wrong byte length. \
Key will be treated as unconfigured — HPKE decryption unavailable."
);
return None;
}
let mut arr = [0u8; 32];
arr.copy_from_slice(&bytes);
Some(arr)
}
pub fn is_configured(&self) -> bool {
self.private_key_bytes().is_some()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_key_returns_none() {
let config = EncryptionConfig::default();
assert!(config.private_key_bytes().is_none());
assert!(!config.is_configured());
}
#[test]
fn valid_hex_key_parses() {
let config = EncryptionConfig {
private_key: "a".repeat(64),
};
assert!(config.private_key_bytes().is_some());
assert!(config.is_configured());
}
#[test]
fn hex_key_with_0x_prefix_parses() {
let config = EncryptionConfig {
private_key: format!("0x{}", "b".repeat(64)),
};
assert!(config.private_key_bytes().is_some());
}
#[test]
fn wrong_length_returns_none() {
let config = EncryptionConfig {
private_key: "abcd".to_string(),
};
assert!(config.private_key_bytes().is_none());
}
#[test]
fn invalid_hex_returns_none() {
let config = EncryptionConfig {
private_key: "xyz".repeat(22),
};
assert!(config.private_key_bytes().is_none());
}
}