latticearc 0.5.1

Production-ready post-quantum cryptography. Hybrid ML-KEM+X25519 by default, all 4 NIST standards (FIPS 203–206), post-quantum TLS, and FIPS 140-3 backend — one crate, zero unsafe.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
//! Error types for LatticeArc Core operations.
//!
//! Provides a comprehensive error enum covering all cryptographic operations,
//! configuration validation, hardware issues, and authentication failures.

#![deny(unsafe_code)]
#![deny(missing_docs)]
#![deny(clippy::unwrap_used)]
#![deny(clippy::panic)]

use thiserror::Error;

// Re-export TypeError from types module so downstream consumers can handle it
// (returned by config validate() methods and CryptoPolicyEngine methods)
pub use crate::types::error::TypeError;

/// Errors that can occur during LatticeArc Core operations.
///
/// This enum covers all error conditions from cryptographic operations,
/// configuration validation, hardware acceleration, and authentication.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum CoreError {
    /// Invalid input provided to an operation.
    #[error("Invalid input: {0}")]
    InvalidInput(String),

    /// Key length does not match expected size.
    #[error("Invalid key length: expected {expected}, got {actual}")]
    InvalidKeyLength {
        /// Expected key length in bytes.
        expected: usize,
        /// Actual key length provided.
        actual: usize,
    },

    /// Encryption operation failed.
    #[error("Encryption failed: {0}")]
    EncryptionFailed(String),

    /// Decryption operation failed.
    #[error("Decryption failed: {0}")]
    DecryptionFailed(String),

    /// Signature verification failed.
    #[error("Signature verification failed")]
    VerificationFailed,

    /// Key derivation function failed.
    #[error("Key derivation failed: {0}")]
    KeyDerivationFailed(String),

    /// Invalid nonce provided.
    #[error("Invalid nonce: {0}")]
    InvalidNonce(String),

    /// Hardware-related error occurred.
    #[error("Hardware error: {0}")]
    HardwareError(String),

    /// Configuration validation error.
    #[error("Configuration error: {0}")]
    ConfigurationError(String),

    /// Cryptographic scheme selection failed.
    #[error("Scheme selection failed: {0}")]
    SchemeSelectionFailed(String),

    /// Authentication operation failed.
    #[error("Authentication failed: {0}")]
    AuthenticationFailed(String),

    /// Zero-trust verification check failed.
    #[error("Zero-trust verification failed: {0}")]
    ZeroTrustVerificationFailed(String),

    /// Zero Trust authentication is required but not provided.
    ///
    /// This error occurs when a cryptographic operation requires a
    /// `VerifiedSession` but none was provided or established.
    #[error("Authentication required: {0}")]
    AuthenticationRequired(String),

    /// The session has expired and needs re-authentication.
    ///
    /// Sessions have a limited lifetime for security. When this error
    /// occurs, establish a new session using `VerifiedSession::establish()`.
    #[error("Session expired")]
    SessionExpired,

    /// Requested operation is not supported.
    #[error("Unsupported operation: {0}")]
    UnsupportedOperation(String),

    /// Memory allocation or management error.
    #[error("Memory allocation failed: {0}")]
    MemoryError(String),

    /// Standard I/O error.
    #[error("IO error: {0}")]
    IoError(#[from] std::io::Error),

    /// ML-KEM cryptographic operation error.
    #[error("ML-KEM error: {0}")]
    MlKemError(#[from] crate::primitives::kem::ml_kem::MlKemError),

    /// ML-DSA signature operation error.
    #[error("ML-DSA error: {0}")]
    MlDsaError(#[from] crate::primitives::sig::ml_dsa::MlDsaError),

    /// SLH-DSA signature operation error.
    #[error("SLH-DSA error: {0}")]
    SlhDsaError(#[from] crate::primitives::sig::slh_dsa::SlhDsaError),

    /// Serialization or deserialization error.
    #[error("Serialization error: {0}")]
    SerializationError(String),

    /// Recoverable error with suggested action.
    #[error("Recoverable error: {message}. Suggestion: {suggestion}")]
    Recoverable {
        /// Error message describing what went wrong.
        message: String,
        /// Suggested action to recover from this error.
        suggestion: String,
    },

    /// Hardware acceleration is unavailable.
    #[error("Hardware acceleration unavailable: {reason}. Fallback: {fallback}")]
    HardwareUnavailable {
        /// Reason why hardware acceleration is unavailable.
        reason: String,
        /// Fallback strategy to use.
        fallback: String,
    },

    /// Entropy source has been depleted.
    #[error("Entropy source depleted: {message}. Action: {action}")]
    EntropyDepleted {
        /// Description of the entropy depletion.
        message: String,
        /// Recommended action to address the issue.
        action: String,
    },

    /// Key generation operation failed.
    #[error("Key generation failed: {reason}. Recovery: {recovery}")]
    KeyGenerationFailed {
        /// Reason for the key generation failure.
        reason: String,
        /// Recovery steps to address the failure.
        recovery: String,
    },

    /// Cryptographic self-test failed.
    #[error("Self-test failed: {component}. Status: {status}")]
    SelfTestFailed {
        /// Component that failed the self-test.
        component: String,
        /// Status or details of the failure.
        status: String,
    },

    /// Requested feature is not available.
    #[error("Feature not available: {0}")]
    FeatureNotAvailable(String),

    /// Invalid signature detected.
    #[error("Invalid signature: {0}")]
    InvalidSignature(String),

    /// Invalid cryptographic key.
    #[error("Invalid key: {0}")]
    InvalidKey(String),

    /// Feature is not yet implemented.
    #[error("Not implemented: {0}")]
    NotImplemented(String),

    /// Signature creation failed.
    #[error("Signature failed: {0}")]
    SignatureFailed(String),

    /// Hardware Security Module error.
    #[error("HSM error: {0}")]
    HsmError(String),

    /// Resource limit has been exceeded.
    #[error("Resource limit exceeded: {0}")]
    ResourceExceeded(String),

    /// Invalid key lifecycle state transition attempted.
    #[error("Invalid key state transition: {from:?} -> {to:?}")]
    InvalidStateTransition {
        /// Original key state.
        from: crate::unified_api::KeyLifecycleState,
        /// Target key state that was rejected.
        to: crate::unified_api::KeyLifecycleState,
    },

    /// Audit storage operation failed.
    #[error("Audit error: {0}")]
    AuditError(String),

    /// Operation violates the configured compliance mode.
    ///
    /// Returned when an algorithm or scheme is not permitted under the active
    /// compliance policy (e.g., FIPS 140-3 rejecting ChaCha20-Poly1305, or
    /// CNSA 2.0 rejecting classical-only algorithms like Ed25519).
    #[error("Compliance violation: {0}")]
    ComplianceViolation(String),
}

/// Conversion from pure-Rust `TypeError` into FFI-aware `CoreError`.
///
/// This allows the `?` operator to work seamlessly when [`types`](crate::types) functions
/// (which return `TypeError`) are called from [`unified_api`](crate::unified_api) functions (which return `CoreError`).
impl From<TypeError> for CoreError {
    fn from(err: TypeError) -> Self {
        match err {
            TypeError::InvalidStateTransition { from, to } => {
                CoreError::InvalidStateTransition { from, to }
            }
            TypeError::ConfigurationError(msg) => CoreError::ConfigurationError(msg),
            TypeError::UnknownScheme(scheme) => {
                CoreError::DecryptionFailed(format!("Unknown encryption scheme: {scheme}"))
            }
        }
    }
}

/// A specialized Result type for LatticeArc Core operations.
pub type Result<T> = std::result::Result<T, CoreError>;

#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
    use super::*;

    #[test]
    fn test_core_error_display_invalid_input_has_correct_format() {
        let err = CoreError::InvalidInput("bad data".to_string());
        assert_eq!(format!("{err}"), "Invalid input: bad data");
    }

    #[test]
    fn test_core_error_display_invalid_key_length_has_correct_format() {
        let err = CoreError::InvalidKeyLength { expected: 32, actual: 16 };
        assert_eq!(format!("{err}"), "Invalid key length: expected 32, got 16");
    }

    #[test]
    fn test_core_error_display_encryption_failed_has_correct_format() {
        let err = CoreError::EncryptionFailed("buffer too small".to_string());
        assert_eq!(format!("{err}"), "Encryption failed: buffer too small");
    }

    #[test]
    fn test_core_error_display_decryption_failed_has_correct_format() {
        let err = CoreError::DecryptionFailed("auth tag mismatch".to_string());
        assert_eq!(format!("{err}"), "Decryption failed: auth tag mismatch");
    }

    #[test]
    fn test_core_error_display_verification_failed_has_correct_format() {
        let err = CoreError::VerificationFailed;
        assert_eq!(format!("{err}"), "Signature verification failed");
    }

    #[test]
    fn test_core_error_display_session_expired_has_correct_format() {
        let err = CoreError::SessionExpired;
        assert_eq!(format!("{err}"), "Session expired");
    }

    #[test]
    fn test_core_error_display_recoverable_includes_message_and_suggestion_fails() {
        let err = CoreError::Recoverable {
            message: "temporary failure".to_string(),
            suggestion: "retry after 5s".to_string(),
        };
        let msg = format!("{err}");
        assert!(msg.contains("temporary failure"));
        assert!(msg.contains("retry after 5s"));
    }

    #[test]
    fn test_core_error_display_hardware_unavailable_includes_reason_and_fallback_fails() {
        let err = CoreError::HardwareUnavailable {
            reason: "no AES-NI".to_string(),
            fallback: "software AES".to_string(),
        };
        let msg = format!("{err}");
        assert!(msg.contains("no AES-NI"));
        assert!(msg.contains("software AES"));
    }

    #[test]
    fn test_core_error_display_entropy_depleted_includes_message_and_action_fails() {
        let err = CoreError::EntropyDepleted {
            message: "pool empty".to_string(),
            action: "wait and retry".to_string(),
        };
        let msg = format!("{err}");
        assert!(msg.contains("pool empty"));
        assert!(msg.contains("wait and retry"));
    }

    #[test]
    fn test_core_error_display_key_generation_failed_includes_reason_and_recovery_fails() {
        let err = CoreError::KeyGenerationFailed {
            reason: "insufficient entropy".to_string(),
            recovery: "seed from HSM".to_string(),
        };
        let msg = format!("{err}");
        assert!(msg.contains("insufficient entropy"));
        assert!(msg.contains("seed from HSM"));
    }

    #[test]
    fn test_core_error_display_self_test_failed_includes_component_and_status_fails() {
        let err = CoreError::SelfTestFailed {
            component: "AES-GCM".to_string(),
            status: "KAT mismatch".to_string(),
        };
        let msg = format!("{err}");
        assert!(msg.contains("AES-GCM"));
        assert!(msg.contains("KAT mismatch"));
    }

    #[test]
    fn test_core_error_display_serialization_has_correct_format() {
        let err = CoreError::SerializationError("invalid JSON".to_string());
        assert_eq!(format!("{err}"), "Serialization error: invalid JSON");
    }

    #[test]
    fn test_core_error_display_feature_not_available_fails() {
        let err = CoreError::FeatureNotAvailable("HSM support".to_string());
        assert_eq!(format!("{err}"), "Feature not available: HSM support");
    }

    #[test]
    fn test_core_error_display_not_implemented_has_correct_format() {
        let err = CoreError::NotImplemented("FN-DSA Level 5".to_string());
        assert_eq!(format!("{err}"), "Not implemented: FN-DSA Level 5");
    }

    #[test]
    fn test_core_error_display_hsm_error_has_correct_format() {
        let err = CoreError::HsmError("connection lost".to_string());
        assert_eq!(format!("{err}"), "HSM error: connection lost");
    }

    #[test]
    fn test_core_error_display_resource_exceeded_has_correct_format() {
        let err = CoreError::ResourceExceeded("max connections".to_string());
        assert_eq!(format!("{err}"), "Resource limit exceeded: max connections");
    }

    #[test]
    fn test_core_error_display_audit_error_has_correct_format() {
        let err = CoreError::AuditError("write failed".to_string());
        assert_eq!(format!("{err}"), "Audit error: write failed");
    }

    #[test]
    fn test_core_error_from_io_error_converts_correctly_fails() {
        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file missing");
        let err: CoreError = io_err.into();
        let msg = format!("{err}");
        assert!(msg.contains("file missing"));
    }

    #[test]
    fn test_core_error_debug_contains_variant_name_fails() {
        let err = CoreError::InvalidInput("test".to_string());
        let debug = format!("{:?}", err);
        assert!(debug.contains("InvalidInput"));
    }

    #[test]
    fn test_core_error_all_simple_variants_have_correct_format_fails() {
        // Ensure all simple string variants produce correct output
        let cases: Vec<(CoreError, &str)> = vec![
            (CoreError::KeyDerivationFailed("kdf".to_string()), "Key derivation failed: kdf"),
            (CoreError::InvalidNonce("nonce".to_string()), "Invalid nonce: nonce"),
            (CoreError::HardwareError("hw".to_string()), "Hardware error: hw"),
            (CoreError::ConfigurationError("cfg".to_string()), "Configuration error: cfg"),
            (CoreError::SchemeSelectionFailed("sel".to_string()), "Scheme selection failed: sel"),
            (CoreError::AuthenticationFailed("auth".to_string()), "Authentication failed: auth"),
            (
                CoreError::ZeroTrustVerificationFailed("zt".to_string()),
                "Zero-trust verification failed: zt",
            ),
            (CoreError::AuthenticationRequired("req".to_string()), "Authentication required: req"),
            (CoreError::UnsupportedOperation("op".to_string()), "Unsupported operation: op"),
            (CoreError::MemoryError("mem".to_string()), "Memory allocation failed: mem"),
            (CoreError::InvalidSignature("sig".to_string()), "Invalid signature: sig"),
            (CoreError::InvalidKey("key".to_string()), "Invalid key: key"),
            (CoreError::SignatureFailed("sf".to_string()), "Signature failed: sf"),
            (
                CoreError::ComplianceViolation("FIPS rejects chacha".to_string()),
                "Compliance violation: FIPS rejects chacha",
            ),
        ];

        for (error, expected_msg) in cases {
            assert_eq!(format!("{error}"), expected_msg);
        }
    }
}