use std::fmt;
#[derive(Debug)]
pub enum CryptoError {
InvalidKeyLength {
algorithm: &'static str,
expected: usize,
got: usize,
},
InvalidKey(String),
InvalidNonceLength {
algorithm: &'static str,
expected: usize,
got: usize,
},
AuthenticationFailed,
Encryption(String),
Decryption(String),
Kdf(String),
Pqc(String),
ReedSolomon(String),
InvalidParameter(String),
Serialization(String),
Compression(String),
Io(std::io::Error),
ChecksumMismatch {
expected: u32,
got: u32,
},
InvalidNormalization {
codepoint: char,
reason: &'static str,
},
RejectedCodepointClass {
codepoint: char,
class: &'static str,
},
InsufficientEntropy {
got_bits: f64,
required_min: f64,
},
RecoveryPhrase(String),
}
impl fmt::Display for CryptoError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CryptoError::InvalidKeyLength {
algorithm,
expected,
got,
} => write!(
f,
"Invalid key length for {algorithm}: expected {expected} bytes, got {got}"
),
CryptoError::InvalidKey(msg) => write!(f, "Invalid key: {msg}"),
CryptoError::InvalidNonceLength {
algorithm,
expected,
got,
} => write!(
f,
"Invalid nonce length for {algorithm}: expected {expected} bytes, got {got}"
),
CryptoError::AuthenticationFailed => write!(
f,
"Authentication failed (wrong key, tampered data, or invalid signature)"
),
CryptoError::Encryption(msg) => write!(f, "Encryption failed: {msg}"),
CryptoError::Decryption(msg) => write!(f, "Decryption failed: {msg}"),
CryptoError::Kdf(msg) => write!(f, "KDF error: {msg}"),
CryptoError::Pqc(msg) => write!(f, "PQC error: {msg}"),
CryptoError::ReedSolomon(msg) => write!(f, "Reed-Solomon error: {msg}"),
CryptoError::InvalidParameter(msg) => write!(f, "Invalid parameter: {msg}"),
CryptoError::Serialization(msg) => write!(f, "Serialization error: {msg}"),
CryptoError::Compression(msg) => write!(f, "Compression error: {msg}"),
CryptoError::Io(err) => write!(f, "I/O error: {err}"),
CryptoError::ChecksumMismatch { expected, got } => write!(
f,
"Recovery phrase checksum mismatch (expected 0x{expected:0width$x}, got 0x{got:0width$x})",
width = 8
),
CryptoError::InvalidNormalization { codepoint, reason } => write!(
f,
"Recovery phrase rejected codepoint U+{:04X}: {reason}",
*codepoint as u32
),
CryptoError::RejectedCodepointClass { codepoint, class } => write!(
f,
"Recovery phrase rejected codepoint U+{:04X}: forbidden class {class}",
*codepoint as u32
),
CryptoError::InsufficientEntropy { got_bits, required_min } => write!(
f,
"Custom alphabet phrase yields {got_bits:.1} bits of entropy, below the {required_min:.1}-bit floor"
),
CryptoError::RecoveryPhrase(msg) => write!(f, "Recovery phrase error: {msg}"),
}
}
}
impl std::error::Error for CryptoError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
CryptoError::Io(err) => Some(err),
_ => None,
}
}
}
impl From<std::io::Error> for CryptoError {
fn from(err: std::io::Error) -> Self {
CryptoError::Io(err)
}
}
impl Clone for CryptoError {
fn clone(&self) -> Self {
match self {
CryptoError::InvalidKeyLength {
algorithm,
expected,
got,
} => CryptoError::InvalidKeyLength {
algorithm,
expected: *expected,
got: *got,
},
CryptoError::InvalidKey(msg) => CryptoError::InvalidKey(msg.clone()),
CryptoError::InvalidNonceLength {
algorithm,
expected,
got,
} => CryptoError::InvalidNonceLength {
algorithm,
expected: *expected,
got: *got,
},
CryptoError::AuthenticationFailed => CryptoError::AuthenticationFailed,
CryptoError::Encryption(msg) => CryptoError::Encryption(msg.clone()),
CryptoError::Decryption(msg) => CryptoError::Decryption(msg.clone()),
CryptoError::Kdf(msg) => CryptoError::Kdf(msg.clone()),
CryptoError::Pqc(msg) => CryptoError::Pqc(msg.clone()),
CryptoError::ReedSolomon(msg) => CryptoError::ReedSolomon(msg.clone()),
CryptoError::InvalidParameter(msg) => CryptoError::InvalidParameter(msg.clone()),
CryptoError::Serialization(msg) => CryptoError::Serialization(msg.clone()),
CryptoError::Compression(msg) => CryptoError::Compression(msg.clone()),
CryptoError::Io(err) => {
CryptoError::Io(std::io::Error::new(err.kind(), err.to_string()))
}
CryptoError::ChecksumMismatch { expected, got } => CryptoError::ChecksumMismatch {
expected: *expected,
got: *got,
},
CryptoError::InvalidNormalization { codepoint, reason } => {
CryptoError::InvalidNormalization {
codepoint: *codepoint,
reason,
}
}
CryptoError::RejectedCodepointClass { codepoint, class } => {
CryptoError::RejectedCodepointClass {
codepoint: *codepoint,
class,
}
}
CryptoError::InsufficientEntropy {
got_bits,
required_min,
} => CryptoError::InsufficientEntropy {
got_bits: *got_bits,
required_min: *required_min,
},
CryptoError::RecoveryPhrase(msg) => CryptoError::RecoveryPhrase(msg.clone()),
}
}
}
pub type Result<T> = std::result::Result<T, CryptoError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_invalid_key_length_display() {
let err = CryptoError::InvalidKeyLength {
algorithm: "Falcon-1024",
expected: 32,
got: 16,
};
let msg = err.to_string();
assert!(msg.contains("Falcon-1024"));
assert!(msg.contains("expected 32"));
assert!(msg.contains("got 16"));
}
#[test]
fn test_auth_failed_display() {
let err = CryptoError::AuthenticationFailed;
assert!(err.to_string().contains("Authentication failed"));
}
#[test]
fn test_clone_roundtrip() {
let err = CryptoError::InvalidKeyLength {
algorithm: "Ed25519",
expected: 32,
got: 31,
};
let cloned = err.clone();
assert_eq!(err.to_string(), cloned.to_string());
}
#[test]
fn test_io_from() {
let io_err = std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "truncated");
let crypto_err: CryptoError = io_err.into();
assert!(matches!(crypto_err, CryptoError::Io(_)));
assert!(crypto_err.to_string().contains("truncated"));
}
}