use std::fmt;
use super::{
Authenticator, ClusterKey, ClusterKeyError, EncryptionFeatureDisabled, Keys, KEY_LEN, TAG_LEN,
VERSION_LEN,
};
#[cfg(feature = "encryption")]
use super::{AEAD_NONCE_LEN, AEAD_TAG_LEN};
use crate::replay::REPLAY_HEADER_LEN;
impl ClusterKey {
pub fn new(bytes: [u8; KEY_LEN]) -> Self {
ClusterKey(bytes)
}
pub fn from_hex(hex: &str) -> Result<Self, ClusterKeyError> {
if hex.len() != KEY_LEN * 2 {
return Err(ClusterKeyError::WrongHexLength(hex.len()));
}
let mut bytes = [0u8; KEY_LEN];
for (i, byte) in bytes.iter_mut().enumerate() {
*byte = u8::from_str_radix(&hex[i * 2..i * 2 + 2], 16)
.map_err(|_| ClusterKeyError::InvalidHexDigit)?;
}
Ok(ClusterKey(bytes))
}
pub(super) fn as_bytes(&self) -> &[u8; KEY_LEN] {
&self.0
}
#[must_use]
pub fn derive_lift_key(&self) -> [u8; KEY_LEN] {
blake3::derive_key(
"reconcile-rs 2026-08-25 rsos::fingerprint lift key",
&self.0,
)
}
}
impl fmt::Debug for ClusterKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("ClusterKey").field(&"<redacted>").finish()
}
}
impl TryFrom<&[u8]> for ClusterKey {
type Error = ClusterKeyError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
<[u8; KEY_LEN]>::try_from(bytes)
.map(ClusterKey)
.map_err(|_| ClusterKeyError::WrongByteLength(bytes.len()))
}
}
impl fmt::Display for ClusterKeyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ClusterKeyError::WrongHexLength(got) => write!(
f,
"cluster key must be {} hex characters, got {got}",
KEY_LEN * 2
),
ClusterKeyError::InvalidHexDigit => {
write!(
f,
"cluster key hex string contains a non-hex-digit character"
)
}
ClusterKeyError::WrongByteLength(got) => {
write!(f, "cluster key must be {KEY_LEN} bytes, got {got}")
}
}
}
}
impl std::error::Error for ClusterKeyError {}
impl fmt::Display for EncryptionFeatureDisabled {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"reconcile: encryption requested but the crate was built without the `encryption` feature"
)
}
}
impl std::error::Error for EncryptionFeatureDisabled {}
impl Keys {
pub fn single(key: ClusterKey) -> Keys {
Keys {
primary: key,
also_accept: Vec::new(),
}
}
pub(super) fn iter(&self) -> impl Iterator<Item = &ClusterKey> {
std::iter::once(&self.primary).chain(self.also_accept.iter())
}
}
impl Authenticator {
pub fn new(key: Option<ClusterKey>, encrypt: bool) -> Result<Self, EncryptionFeatureDisabled> {
Self::with_rotation(key.map(Keys::single), encrypt)
}
pub fn with_rotation(
keys: Option<Keys>,
encrypt: bool,
) -> Result<Self, EncryptionFeatureDisabled> {
Ok(match (keys, encrypt) {
(None, _) => Authenticator::Disabled,
(Some(keys), false) => Authenticator::Enabled(keys),
#[cfg(feature = "encryption")]
(Some(keys), true) => Authenticator::Encrypted(keys),
#[cfg(not(feature = "encryption"))]
(Some(_), true) => return Err(EncryptionFeatureDisabled),
})
}
pub fn overhead(&self) -> usize {
match self {
Authenticator::Disabled => VERSION_LEN,
Authenticator::Enabled(_) => TAG_LEN + VERSION_LEN + REPLAY_HEADER_LEN,
#[cfg(feature = "encryption")]
Authenticator::Encrypted(_) => {
AEAD_NONCE_LEN + VERSION_LEN + REPLAY_HEADER_LEN + AEAD_TAG_LEN
}
}
}
}