1use std::fmt;
27
28#[derive(Debug)]
32pub enum CryptoError {
33 InvalidKeyLength {
38 algorithm: &'static str,
40 expected: usize,
42 got: usize,
44 },
45
46 InvalidKey(String),
51
52 InvalidNonceLength {
54 algorithm: &'static str,
56 expected: usize,
58 got: usize,
60 },
61
62 AuthenticationFailed,
68
69 Encryption(String),
72
73 Decryption(String),
75
76 Kdf(String),
78
79 Pqc(String),
81
82 ReedSolomon(String),
84
85 InvalidParameter(String),
87
88 Serialization(String),
90
91 Compression(String),
93
94 Io(std::io::Error),
96
97 ChecksumMismatch {
101 expected: u32,
103 got: u32,
105 },
106
107 InvalidNormalization {
111 codepoint: char,
113 reason: &'static str,
115 },
116
117 RejectedCodepointClass {
121 codepoint: char,
123 class: &'static str,
127 },
128
129 InsufficientEntropy {
132 got_bits: f64,
134 required_min: f64,
136 },
137
138 RecoveryPhrase(String),
141}
142
143impl fmt::Display for CryptoError {
144 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145 match self {
146 CryptoError::InvalidKeyLength {
147 algorithm,
148 expected,
149 got,
150 } => write!(
151 f,
152 "Invalid key length for {algorithm}: expected {expected} bytes, got {got}"
153 ),
154 CryptoError::InvalidKey(msg) => write!(f, "Invalid key: {msg}"),
155 CryptoError::InvalidNonceLength {
156 algorithm,
157 expected,
158 got,
159 } => write!(
160 f,
161 "Invalid nonce length for {algorithm}: expected {expected} bytes, got {got}"
162 ),
163 CryptoError::AuthenticationFailed => write!(
164 f,
165 "Authentication failed (wrong key, tampered data, or invalid signature)"
166 ),
167 CryptoError::Encryption(msg) => write!(f, "Encryption failed: {msg}"),
168 CryptoError::Decryption(msg) => write!(f, "Decryption failed: {msg}"),
169 CryptoError::Kdf(msg) => write!(f, "KDF error: {msg}"),
170 CryptoError::Pqc(msg) => write!(f, "PQC error: {msg}"),
171 CryptoError::ReedSolomon(msg) => write!(f, "Reed-Solomon error: {msg}"),
172 CryptoError::InvalidParameter(msg) => write!(f, "Invalid parameter: {msg}"),
173 CryptoError::Serialization(msg) => write!(f, "Serialization error: {msg}"),
174 CryptoError::Compression(msg) => write!(f, "Compression error: {msg}"),
175 CryptoError::Io(err) => write!(f, "I/O error: {err}"),
176 CryptoError::ChecksumMismatch { expected, got } => write!(
177 f,
178 "Recovery phrase checksum mismatch (expected 0x{expected:0width$x}, got 0x{got:0width$x})",
179 width = 8
180 ),
181 CryptoError::InvalidNormalization { codepoint, reason } => write!(
182 f,
183 "Recovery phrase rejected codepoint U+{:04X}: {reason}",
184 *codepoint as u32
185 ),
186 CryptoError::RejectedCodepointClass { codepoint, class } => write!(
187 f,
188 "Recovery phrase rejected codepoint U+{:04X}: forbidden class {class}",
189 *codepoint as u32
190 ),
191 CryptoError::InsufficientEntropy { got_bits, required_min } => write!(
192 f,
193 "Custom alphabet phrase yields {got_bits:.1} bits of entropy, below the {required_min:.1}-bit floor"
194 ),
195 CryptoError::RecoveryPhrase(msg) => write!(f, "Recovery phrase error: {msg}"),
196 }
197 }
198}
199
200impl std::error::Error for CryptoError {
201 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
202 match self {
203 CryptoError::Io(err) => Some(err),
204 _ => None,
205 }
206 }
207}
208
209impl From<std::io::Error> for CryptoError {
210 fn from(err: std::io::Error) -> Self {
211 CryptoError::Io(err)
212 }
213}
214
215impl Clone for CryptoError {
216 fn clone(&self) -> Self {
217 match self {
218 CryptoError::InvalidKeyLength {
219 algorithm,
220 expected,
221 got,
222 } => CryptoError::InvalidKeyLength {
223 algorithm,
224 expected: *expected,
225 got: *got,
226 },
227 CryptoError::InvalidKey(msg) => CryptoError::InvalidKey(msg.clone()),
228 CryptoError::InvalidNonceLength {
229 algorithm,
230 expected,
231 got,
232 } => CryptoError::InvalidNonceLength {
233 algorithm,
234 expected: *expected,
235 got: *got,
236 },
237 CryptoError::AuthenticationFailed => CryptoError::AuthenticationFailed,
238 CryptoError::Encryption(msg) => CryptoError::Encryption(msg.clone()),
239 CryptoError::Decryption(msg) => CryptoError::Decryption(msg.clone()),
240 CryptoError::Kdf(msg) => CryptoError::Kdf(msg.clone()),
241 CryptoError::Pqc(msg) => CryptoError::Pqc(msg.clone()),
242 CryptoError::ReedSolomon(msg) => CryptoError::ReedSolomon(msg.clone()),
243 CryptoError::InvalidParameter(msg) => CryptoError::InvalidParameter(msg.clone()),
244 CryptoError::Serialization(msg) => CryptoError::Serialization(msg.clone()),
245 CryptoError::Compression(msg) => CryptoError::Compression(msg.clone()),
246 CryptoError::Io(err) => {
247 CryptoError::Io(std::io::Error::new(err.kind(), err.to_string()))
248 }
249 CryptoError::ChecksumMismatch { expected, got } => CryptoError::ChecksumMismatch {
250 expected: *expected,
251 got: *got,
252 },
253 CryptoError::InvalidNormalization { codepoint, reason } => {
254 CryptoError::InvalidNormalization {
255 codepoint: *codepoint,
256 reason,
257 }
258 }
259 CryptoError::RejectedCodepointClass { codepoint, class } => {
260 CryptoError::RejectedCodepointClass {
261 codepoint: *codepoint,
262 class,
263 }
264 }
265 CryptoError::InsufficientEntropy {
266 got_bits,
267 required_min,
268 } => CryptoError::InsufficientEntropy {
269 got_bits: *got_bits,
270 required_min: *required_min,
271 },
272 CryptoError::RecoveryPhrase(msg) => CryptoError::RecoveryPhrase(msg.clone()),
273 }
274 }
275}
276
277pub type Result<T> = std::result::Result<T, CryptoError>;
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283
284 #[test]
285 fn test_invalid_key_length_display() {
286 let err = CryptoError::InvalidKeyLength {
287 algorithm: "Falcon-1024",
288 expected: 32,
289 got: 16,
290 };
291 let msg = err.to_string();
292 assert!(msg.contains("Falcon-1024"));
293 assert!(msg.contains("expected 32"));
294 assert!(msg.contains("got 16"));
295 }
296
297 #[test]
298 fn test_auth_failed_display() {
299 let err = CryptoError::AuthenticationFailed;
300 assert!(err.to_string().contains("Authentication failed"));
301 }
302
303 #[test]
304 fn test_clone_roundtrip() {
305 let err = CryptoError::InvalidKeyLength {
306 algorithm: "Ed25519",
307 expected: 32,
308 got: 31,
309 };
310 let cloned = err.clone();
311 assert_eq!(err.to_string(), cloned.to_string());
312 }
313
314 #[test]
315 fn test_io_from() {
316 let io_err = std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "truncated");
317 let crypto_err: CryptoError = io_err.into();
318 assert!(matches!(crypto_err, CryptoError::Io(_)));
319 assert!(crypto_err.to_string().contains("truncated"));
320 }
321}