kimberlite-crypto 0.4.2

Cryptographic primitives for Kimberlite
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
413
//! Ed25519 digital signatures for record non-repudiation.
//!
//! Provides thin, type-safe wrappers around `ed25519-dalek` for signing
//! and verifying records. Each record in the audit log can be signed to
//! prove authorship and detect tampering.
//!
//! # Example
//!
//! ```
//! use kimberlite_crypto::signature::{SigningKey, Signature};
//!
//! // Generate a new signing key
//! let signing_key = SigningKey::generate();
//! let verifying_key = signing_key.verifying_key();
//!
//! // Sign a message
//! let message = b"audit record data";
//! let signature = signing_key.sign(message);
//!
//! // Verify the signature
//! assert!(verifying_key.verify(message, &signature).is_ok());
//!
//! // Tampered messages fail verification
//! let tampered = b"tampered data";
//! assert!(verifying_key.verify(tampered, &signature).is_err());
//! ```

use ed25519_dalek::{PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH, SIGNATURE_LENGTH, Signer};
use rand::rngs::OsRng;
use zeroize::ZeroizeOnDrop;

use crate::error::CryptoError;

// ============================================================================
// Constants
// ============================================================================

/// Length of a signing key in bytes (32 bytes).
pub const SIGNING_KEY_LENGTH: usize = SECRET_KEY_LENGTH;

/// Length of a verifying key in bytes (32 bytes).
pub const VERIFYING_KEY_LENGTH: usize = PUBLIC_KEY_LENGTH;

// ============================================================================
// SigningKey
// ============================================================================

/// An Ed25519 signing key for creating digital signatures.
///
/// This is the secret key that must be kept confidential. Use [`SigningKey::generate`]
/// to create a new random key, or [`SigningKey::from_bytes`] to restore from storage.
///
/// # Security
///
/// - Never log or expose the key bytes
/// - Store encrypted at rest
/// - Use one key per identity/purpose
#[derive(Clone, ZeroizeOnDrop)]
pub struct SigningKey(ed25519_dalek::SigningKey);

impl SigningKey {
    // ========================================================================
    // Functional Core (pure, testable)
    // ========================================================================

    /// Creates a signing key from random bytes (pure, no IO).
    ///
    /// This is the functional core - it performs no IO and is fully testable.
    /// Use [`Self::generate`] for the public API that handles randomness.
    ///
    /// # Security
    ///
    /// Only use bytes from a CSPRNG. This is `pub(crate)` to prevent misuse.
    pub(crate) fn from_random_bytes(bytes: [u8; SIGNING_KEY_LENGTH]) -> Self {
        // Precondition: caller provided non-degenerate random bytes
        assert!(
            bytes.iter().any(|&b| b != 0),
            "SigningKey random bytes are all zeros - RNG failure or bug"
        );

        Self(ed25519_dalek::SigningKey::from_bytes(&bytes))
    }

    /// Restores a signing key from its 32-byte representation.
    ///
    /// # Note
    ///
    /// Any 32 bytes form a valid Ed25519 secret key, so this cannot fail.
    /// However, you should only use bytes from a previously generated key.
    pub fn from_bytes(bytes: &[u8; SIGNING_KEY_LENGTH]) -> Self {
        Self(ed25519_dalek::SigningKey::from_bytes(bytes))
    }

    // ========================================================================
    // Imperative Shell (IO boundary)
    // ========================================================================

    /// Generates a new random signing key using the OS CSPRNG.
    ///
    /// This is the imperative shell - it handles IO (randomness) and delegates
    /// to a pure internal constructor for the actual construction.
    ///
    /// # Panics
    ///
    /// Panics if the OS CSPRNG fails (should never happen on supported platforms).
    pub fn generate() -> Self {
        let mut csprng = OsRng;
        let key = ed25519_dalek::SigningKey::generate(&mut csprng);
        Self::from_random_bytes(key.to_bytes())
    }

    /// Returns the corresponding public key for signature verification.
    ///
    /// The verifying key can be shared publicly and used by others to verify
    /// signatures created with this signing key.
    pub fn verifying_key(&self) -> VerifyingKey {
        VerifyingKey(self.0.verifying_key())
    }

    /// Signs a message, producing a 64-byte signature.
    ///
    /// The signature proves that the holder of this signing key authored
    /// the message. Anyone with the corresponding [`VerifyingKey`] can
    /// verify the signature.
    ///
    /// # Arguments
    ///
    /// * `message` - The data to sign (typically a serialized record)
    ///
    /// # Returns
    ///
    /// A [`Signature`] that can be verified with [`VerifyingKey::verify`].
    pub fn sign(&self, message: &[u8]) -> Signature {
        // Precondition: message length is reasonable for signing
        // (Ed25519 can sign any length, but we assert it's not absurdly large)
        debug_assert!(
            message.len() <= 64 * 1024 * 1024,
            "message exceeds 64MB sanity limit"
        );

        let signature = self.0.sign(message);

        // Postcondition: signature has correct length and isn't degenerate
        let sig_bytes = signature.to_bytes();
        assert_eq!(
            sig_bytes.len(),
            SIGNATURE_LENGTH,
            "signature length mismatch: expected {}, got {}",
            SIGNATURE_LENGTH,
            sig_bytes.len()
        );
        assert!(
            sig_bytes.iter().any(|&b| b != 0),
            "signature is all zeros - cryptographic library bug"
        );

        Signature(signature)
    }

    /// Returns the raw 32-byte key material.
    ///
    /// # Security
    ///
    /// Handle with care — this is secret key material.
    pub fn to_bytes(&self) -> [u8; SIGNING_KEY_LENGTH] {
        self.0.to_bytes()
    }
}

// ============================================================================
// VerifyingKey
// ============================================================================

/// An Ed25519 public key for verifying digital signatures.
///
/// This is the public key that can be freely shared. It can verify signatures
/// created by the corresponding [`SigningKey`] but cannot create new signatures.
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct VerifyingKey(ed25519_dalek::VerifyingKey);

impl VerifyingKey {
    /// Parses a verifying key from its 32-byte representation.
    ///
    /// # Errors
    ///
    /// Returns [`CryptoError::SignatureError`] if the bytes don't represent
    /// a valid Ed25519 public key point.
    pub fn from_bytes(bytes: &[u8; VERIFYING_KEY_LENGTH]) -> Result<Self, CryptoError> {
        // Precondition: bytes aren't all zeros (common mistake)
        assert!(
            bytes.iter().any(|&b| b != 0),
            "verifying key bytes are all zeros - corrupted or uninitialized key"
        );

        let key = ed25519_dalek::VerifyingKey::from_bytes(bytes)?;
        Ok(Self(key))
    }

    /// Verifies a signature against a message.
    ///
    /// Uses strict verification which rejects non-canonical signatures,
    /// providing stronger security guarantees.
    ///
    /// # Arguments
    ///
    /// * `message` - The original message that was signed
    /// * `signature` - The signature to verify
    ///
    /// # Errors
    ///
    /// Returns [`CryptoError::SignatureError`] if the signature is invalid.
    pub fn verify(&self, message: &[u8], signature: &Signature) -> Result<(), CryptoError> {
        // Precondition: message length matches signing expectations
        debug_assert!(
            message.len() <= 64 * 1024 * 1024,
            "message exceeds 64MB sanity limit"
        );

        self.0.verify_strict(message, &signature.0)?;
        Ok(())
    }

    /// Returns the raw 32-byte public key.
    pub fn to_bytes(&self) -> [u8; VERIFYING_KEY_LENGTH] {
        self.0.to_bytes()
    }
}

impl std::fmt::Debug for VerifyingKey {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let bytes = self.to_bytes();
        write!(
            f,
            "VerifyingKey({:02x}{:02x}{:02x}{:02x}...)",
            bytes[0], bytes[1], bytes[2], bytes[3]
        )
    }
}

// ============================================================================
// Signature
// ============================================================================

/// An Ed25519 signature (64 bytes).
///
/// A signature proves that a message was signed by the holder of a particular
/// signing key. Use [`VerifyingKey::verify`] to validate signatures.
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Signature(ed25519_dalek::Signature);

impl Signature {
    /// Parses a signature from its 64-byte representation.
    ///
    /// # Note
    ///
    /// This only checks structural validity (correct length). The signature
    /// may still fail cryptographic verification — use [`VerifyingKey::verify`]
    /// to validate against a message.
    pub fn from_bytes(bytes: &[u8; SIGNATURE_LENGTH]) -> Self {
        // Precondition: signature bytes aren't all zeros (likely bug)
        assert!(
            bytes.iter().any(|&b| b != 0),
            "signature bytes are all zeros - corrupted or uninitialized signature"
        );

        Self(ed25519_dalek::Signature::from_bytes(bytes))
    }

    /// Returns the raw 64-byte signature.
    pub fn to_bytes(&self) -> [u8; SIGNATURE_LENGTH] {
        self.0.to_bytes()
    }
}

impl std::fmt::Debug for Signature {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let bytes = self.to_bytes();
        write!(
            f,
            "Signature({:02x}{:02x}{:02x}{:02x}...)",
            bytes[0], bytes[1], bytes[2], bytes[3]
        )
    }
}

// ============================================================================
// Tests
// ============================================================================

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn sign_and_verify_roundtrip() {
        let signing_key = SigningKey::generate();
        let verifying_key = signing_key.verifying_key();

        let message = b"test message for signing";
        let signature = signing_key.sign(message);

        // Verification should succeed
        assert!(verifying_key.verify(message, &signature).is_ok());
    }

    #[test]
    fn tampered_message_fails_verification() {
        let signing_key = SigningKey::generate();
        let verifying_key = signing_key.verifying_key();

        let message = b"original message";
        let signature = signing_key.sign(message);

        // Tampered message should fail
        let tampered = b"tampered message";
        assert!(verifying_key.verify(tampered, &signature).is_err());
    }

    #[test]
    fn tampered_signature_fails_verification() {
        let signing_key = SigningKey::generate();
        let verifying_key = signing_key.verifying_key();

        let message = b"test message";
        let signature = signing_key.sign(message);

        // Flip a bit in the signature
        let mut bad_sig_bytes = signature.to_bytes();
        bad_sig_bytes[0] ^= 0x01;
        let bad_signature = Signature::from_bytes(&bad_sig_bytes);

        assert!(verifying_key.verify(message, &bad_signature).is_err());
    }

    #[test]
    fn wrong_key_fails_verification() {
        let signing_key = SigningKey::generate();
        let wrong_key = SigningKey::generate();

        let message = b"test message";
        let signature = signing_key.sign(message);

        // Different key should fail
        assert!(
            wrong_key
                .verifying_key()
                .verify(message, &signature)
                .is_err()
        );
    }

    #[test]
    fn signing_key_roundtrip() {
        let original = SigningKey::generate();
        let bytes = original.to_bytes();
        let restored = SigningKey::from_bytes(&bytes);

        // Should produce same verifying key
        assert_eq!(
            original.verifying_key().to_bytes(),
            restored.verifying_key().to_bytes()
        );

        // Should produce same signatures
        let message = b"test";
        let sig1 = original.sign(message);
        let sig2 = restored.sign(message);
        assert_eq!(sig1.to_bytes(), sig2.to_bytes());
    }

    #[test]
    fn verifying_key_roundtrip() {
        let signing_key = SigningKey::generate();
        let original = signing_key.verifying_key();

        let bytes = original.to_bytes();
        let restored = VerifyingKey::from_bytes(&bytes).unwrap();

        assert_eq!(original, restored);
    }

    #[test]
    fn signature_roundtrip() {
        let signing_key = SigningKey::generate();
        let signature = signing_key.sign(b"test");

        let bytes = signature.to_bytes();
        let restored = Signature::from_bytes(&bytes);

        assert_eq!(signature, restored);
    }

    #[test]
    fn empty_message_can_be_signed() {
        let signing_key = SigningKey::generate();
        let verifying_key = signing_key.verifying_key();

        let signature = signing_key.sign(b"");
        assert!(verifying_key.verify(b"", &signature).is_ok());
    }

    #[test]
    fn deterministic_signatures() {
        // Ed25519 signatures are deterministic (same key + message = same signature)
        let signing_key = SigningKey::generate();
        let message = b"determinism test";

        let sig1 = signing_key.sign(message);
        let sig2 = signing_key.sign(message);

        assert_eq!(sig1.to_bytes(), sig2.to_bytes());
    }
}