Skip to content

Commit ebcdf89

Browse files
committed
[feat] ECDSA traits - Make serialization/desserialization of private keys an optional composable behavior.
1 parent 7c4a936 commit ebcdf89

File tree

1 file changed

+163
-101
lines changed

1 file changed

+163
-101
lines changed

hal/blocking/src/ecdsa.rs

Lines changed: 163 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
//! - **Security First**: Mandatory cryptographic RNG, proper key validation, secure memory clearing
1313
//! - **No-std Compatible**: Works in embedded environments without standard library
1414
//! - **Comprehensive Error Handling**: Detailed error types for proper debugging and security
15-
//! - **Zero-copy Serialization**: Efficient serialization using `zerocopy` traits
15+
//! - **Optional Serialization**: Flexible serialization support for software keys, with hardware key compatibility
1616
//!
1717
//! ## Architecture
1818
//!
@@ -24,11 +24,14 @@
2424
//! └── Scalar: IntoBytes + FromBytes
2525
//!
2626
//! Key Management
27-
//! ├── PrivateKey<C>: Zeroize + Serialization + Validation
28-
//! └── PublicKey<C>: Serialization + Coordinate Access + Validation
27+
//! ├── PrivateKey<C>: Zeroize + Validation
28+
//! ├── SerializablePrivateKey<C>: PrivateKey<C> + Serialization (optional)
29+
//! ├── PublicKey<C>: Coordinate Access + Validation
30+
//! └── SerializablePublicKey<C>: PublicKey<C> + Serialization (optional)
2931
//!
3032
//! Signatures
31-
//! └── Signature<C>: Serialization + Component Access + Validation
33+
//! ├── Signature<C>: Component Access + Validation
34+
//! └── SerializableSignature<C>: Signature<C> + Serialization (optional)
3235
//!
3336
//! Operations
3437
//! ├── EcdsaKeyGen<C>: Key pair generation
@@ -39,6 +42,11 @@
3942
//! ├── Error: Debug → ErrorKind mapping
4043
//! ├── ErrorType: Associated error types
4144
//! └── ErrorKind: Common error classifications
45+
//!
46+
//! Key Storage Patterns
47+
//! ├── Software Keys: Implement SerializablePrivateKey for persistence
48+
//! ├── Hardware Keys: Implement only PrivateKey for HSM/secure enclave
49+
//! └── Hybrid Systems: Mix both patterns as needed
4250
//! ```
4351
//!
4452
//! ## Usage Example
@@ -48,26 +56,30 @@
4856
//! # use rand_core::{RngCore, CryptoRng};
4957
//! #
5058
//! # fn example() -> Result<(), Box<dyn std::error::Error>> {
51-
//! // This example shows the basic pattern for ECDSA operations
52-
//! // Actual implementations would provide concrete curve types
59+
//! // This example shows both software and hardware key patterns
5360
//!
54-
//! // Key generation pattern:
55-
//! // let mut key_generator = YourKeyGenImpl::new();
61+
//! // SOFTWARE KEYS (with optional serialization):
62+
//! // let mut key_generator = SoftwareKeyGen::new();
5663
//! // let mut rng = YourCryptoRng::new();
5764
//! // let (private_key, public_key) = key_generator.generate_keypair(&mut rng)?;
58-
//!
59-
//! // Key validation pattern:
60-
//! // private_key.validate()?;
61-
//! // public_key.validate()?;
62-
//!
63-
//! // Signing pattern:
65+
//!
66+
//! // Software keys can be serialized:
67+
//! // let private_bytes = private_key.as_bytes(); // SerializablePrivateKey
68+
//! // let public_bytes = public_key.as_bytes(); // SerializablePublicKey
69+
//!
70+
//! // HARDWARE KEYS (no serialization):
71+
//! // let mut hsm_generator = HsmKeyGen::new();
72+
//! // let (hsm_private, hsm_public) = hsm_generator.generate_keypair(&mut rng)?;
73+
//!
74+
//! // Hardware keys cannot be serialized (no IntoBytes/FromBytes bounds)
75+
//! // but can still be used for cryptographic operations:
76+
//! // hsm_private.validate()?; // PrivateKey trait method
77+
//!
78+
//! // Both key types work the same for crypto operations:
6479
//! // let mut signer = YourSignerImpl::new();
6580
//! // let message_digest = your_hash_function(message);
6681
//! // let signature = signer.sign(&private_key, &message_digest, &mut rng)?;
67-
//!
68-
//! // Verification pattern:
69-
//! // let mut verifier = YourVerifierImpl::new();
70-
//! // let is_valid = verifier.verify(&public_key, &message_digest, &signature)?;
82+
//! // let signature_hsm = signer.sign(&hsm_private, &message_digest, &mut rng)?;
7183
//!
7284
//! # Ok(())
7385
//! # }
@@ -78,6 +90,7 @@
7890
//! - **Always validate inputs**: Use the `validate()` methods on keys and signatures
7991
//! - **Use cryptographic RNG**: Only `CryptoRng + RngCore` is accepted for signing
8092
//! - **Clear sensitive data**: Private keys implement `Zeroize` for secure memory clearing
93+
//! - **Hardware key support**: Private keys can be non-serializable for HSM/secure enclave use
8194
//! - **Constant-time operations**: Implementers should use constant-time algorithms where possible
8295
//! - **Side-channel protection**: Be aware of timing attacks in verification operations
8396
@@ -276,16 +289,28 @@ pub enum ErrorKind {
276289
/// - Validate keys are within the valid scalar range (1 < key < curve_order)
277290
/// - Implement constant-time operations where possible
278291
/// - Clear sensitive data from memory using [`Zeroize`]
292+
/// - Hardware keys may store only handles/references, not actual key material
293+
///
294+
/// # Hardware Key Considerations
295+
///
296+
/// For hardware-based keys (HSMs, TPMs, secure enclaves):
297+
/// - The `Zeroize` implementation may clear key handles/references
298+
/// - Actual key material remains securely stored in hardware
299+
/// - Consider calling hardware APIs to invalidate keys during zeroization
300+
/// - Some implementations may use no-op zeroization if hardware manages lifecycle
279301
///
280302
/// # Example
281303
///
282304
/// ```rust,ignore
283-
/// use openprot_hal_blocking::ecdsa::{PrivateKey, Curve, ErrorKind};
305+
/// use openprot_hal_blocking::ecdsa::{PrivateKey, SerializablePrivateKey, Curve, ErrorKind};
284306
/// use zeroize::Zeroize;
307+
/// use zerocopy::{IntoBytes, FromBytes};
285308
///
286-
/// struct MyPrivateKey([u8; 32]);
309+
/// // Software private key (can be serialized)
310+
/// #[derive(IntoBytes, FromBytes)]
311+
/// struct SoftwarePrivateKey([u8; 32]);
287312
///
288-
/// impl<C: Curve> PrivateKey<C> for MyPrivateKey {
313+
/// impl<C: Curve> PrivateKey<C> for SoftwarePrivateKey {
289314
/// fn validate(&self) -> Result<(), ErrorKind> {
290315
/// // Check if key is zero or equal to curve order
291316
/// if self.0.iter().all(|&b| b == 0) {
@@ -294,8 +319,26 @@ pub enum ErrorKind {
294319
/// Ok(())
295320
/// }
296321
/// }
322+
///
323+
/// // Implement optional serialization
324+
/// impl<C: Curve> SerializablePrivateKey<C> for SoftwarePrivateKey {}
325+
///
326+
/// // Hardware private key (cannot be serialized)
327+
/// struct HardwarePrivateKey {
328+
/// key_handle: u32, // Handle to key in HSM
329+
/// }
330+
///
331+
/// impl<C: Curve> PrivateKey<C> for HardwarePrivateKey {
332+
/// fn validate(&self) -> Result<(), ErrorKind> {
333+
/// // Validate key exists in hardware
334+
/// // (implementation would call into HSM API)
335+
/// Ok(())
336+
/// }
337+
/// }
338+
///
339+
/// // No SerializablePrivateKey implementation for hardware keys
297340
/// ```
298-
pub trait PrivateKey<C: Curve>: IntoBytes + FromBytes + Zeroize {
341+
pub trait PrivateKey<C: Curve>: Zeroize {
299342
/// Validate that this private key is valid for the curve.
300343
///
301344
/// This method should verify that the private key is within the valid
@@ -364,33 +407,22 @@ pub trait Curve {
364407
/// }
365408
///
366409
/// impl<C: Curve> Signature<C> for MySignature {
367-
/// fn r(&self) -> &C::Scalar {
368-
/// // Return reference to r component
369-
/// unimplemented!()
370-
/// }
371-
///
372-
/// fn s(&self) -> &C::Scalar {
373-
/// // Return reference to s component
374-
/// unimplemented!()
375-
/// }
376-
///
377-
/// fn from_components(r: C::Scalar, s: C::Scalar) -> Result<Self, ErrorKind> {
378-
/// // Validate components and create signature
379-
/// unimplemented!()
410+
/// fn from_coordinates(r: C::Scalar, s: C::Scalar) -> Result<Self, ErrorKind> {
411+
/// // Validate that r and s are in valid range [1, curve_order)
412+
/// if r.iter().all(|&b| b == 0) || s.iter().all(|&b| b == 0) {
413+
/// return Err(ErrorKind::InvalidSignature);
414+
/// }
415+
/// Ok(Self { r, s })
380416
/// }
381417
///
382-
/// fn new_unchecked(r: C::Scalar, s: C::Scalar) -> Self {
383-
/// // Create signature without validation (use with caution)
384-
/// unimplemented!()
418+
/// fn coordinates(&self, r_out: &mut C::Scalar, s_out: &mut C::Scalar) {
419+
/// // Zero-allocation coordinate access for embedded environments
420+
/// *r_out = self.r;
421+
/// *s_out = self.s;
385422
/// }
386423
/// }
387424
/// ```
388-
pub trait Signature<C: Curve>: IntoBytes + FromBytes {
389-
/// Get the r component of the signature.
390-
fn r(&self) -> &C::Scalar;
391-
/// Get the s component of the signature.
392-
fn s(&self) -> &C::Scalar;
393-
425+
pub trait Signature<C: Curve> {
394426
/// Create a new signature from r and s components with validation.
395427
///
396428
/// This method validates that both r and s are within the valid range
@@ -403,24 +435,17 @@ pub trait Signature<C: Curve>: IntoBytes + FromBytes {
403435
/// # Returns
404436
/// - `Ok(Self)`: Valid signature
405437
/// - `Err(ErrorKind::InvalidSignature)`: If r or s are invalid (zero or ≥ curve order)
406-
fn from_components(r: C::Scalar, s: C::Scalar) -> Result<Self, ErrorKind>
438+
fn from_coordinates(r: C::Scalar, s: C::Scalar) -> Result<Self, ErrorKind>
407439
where
408440
Self: Sized;
409441

410-
/// Validate that this signature has valid r and s components.
411-
///
412-
/// # Returns
413-
/// - `Ok(())`: The signature components are valid
414-
/// - `Err(ErrorKind::InvalidSignature)`: Invalid r or s component
415-
fn validate(&self) -> Result<(), ErrorKind>;
416442

417-
/// Create a new signature from r and s components without validation.
443+
/// Write coordinates to output buffers (zero-allocation for embedded).
418444
///
419-
/// # Safety
420-
/// This method should only be used when the caller can guarantee that
421-
/// r and s are valid for the curve. Use `from_components` for safe
422-
/// construction with validation.
423-
fn new_unchecked(r: C::Scalar, s: C::Scalar) -> Self;
445+
/// This method provides zero-allocation coordinate access for embedded
446+
/// environments. Implementations can extract coordinates directly into
447+
/// the provided buffers without temporary allocation.
448+
fn coordinates(&self, x_out: &mut C::Scalar, y_out: &mut C::Scalar);
424449
}
425450

426451
/// A trait representing a public key associated with a specific elliptic curve.
@@ -447,41 +472,32 @@ pub trait Signature<C: Curve>: IntoBytes + FromBytes {
447472
/// }
448473
///
449474
/// impl<C: Curve> PublicKey<C> for MyPublicKey {
450-
/// fn x(&self) -> &C::Scalar {
451-
/// // Return reference to x coordinate
452-
/// unimplemented!()
453-
/// }
454-
///
455-
/// fn y(&self) -> &C::Scalar {
456-
/// // Return reference to y coordinate
457-
/// unimplemented!()
475+
/// fn coordinates(&self, x_out: &mut C::Scalar, y_out: &mut C::Scalar) {
476+
/// // Zero-allocation coordinate access
477+
/// *x_out = self.x;
478+
/// *y_out = self.y;
458479
/// }
459480
///
460481
/// fn from_coordinates(x: C::Scalar, y: C::Scalar) -> Result<Self, ErrorKind> {
461-
/// // Validate point is on curve and create public key
462-
/// unimplemented!()
463-
/// }
464-
///
465-
/// fn validate(&self) -> Result<(), ErrorKind> {
466-
/// // Check if point lies on the curve
467-
/// unimplemented!()
468-
/// }
469-
///
470-
/// fn new_unchecked(x: C::Scalar, y: C::Scalar) -> Self {
471-
/// // Create key without validation (use with caution)
472-
/// unimplemented!()
482+
/// // Validate point is on curve during construction
483+
/// // (validation logic would go here)
484+
/// Ok(Self { x, y })
473485
/// }
474486
/// }
475487
/// ```
476-
pub trait PublicKey<C: Curve>: IntoBytes + FromBytes {
477-
/// Get the x coordinate of the public key.
478-
fn x(&self) -> &C::Scalar;
479-
/// Get the y coordinate of the public key.
480-
fn y(&self) -> &C::Scalar;
488+
pub trait PublicKey<C: Curve> {
489+
/// Write coordinates to output buffers (zero-allocation for embedded).
490+
///
491+
/// This method provides zero-allocation coordinate access for embedded
492+
/// environments. Implementations can extract coordinates directly into
493+
/// the provided buffers without temporary allocation.
494+
fn coordinates(&self, x_out: &mut C::Scalar, y_out: &mut C::Scalar);
481495

482496
/// Create a new public key from x and y coordinates with validation.
483497
///
484-
/// This method validates that the point (x, y) lies on the specified curve.
498+
/// This method validates that the point (x, y) lies on the specified curve
499+
/// and is cryptographically valid. Validation is performed during construction
500+
/// to ensure all created public keys are valid.
485501
///
486502
/// # Parameters
487503
/// - `x`: The x coordinate of the point
@@ -494,26 +510,72 @@ pub trait PublicKey<C: Curve>: IntoBytes + FromBytes {
494510
fn from_coordinates(x: C::Scalar, y: C::Scalar) -> Result<Self, ErrorKind>
495511
where
496512
Self: Sized;
513+
}
497514

498-
/// Validate that this public key represents a valid point on the curve.
499-
///
500-
/// This method should be called before using a public key for verification
501-
/// to ensure it represents a valid curve point and is not a weak key.
502-
///
503-
/// # Returns
504-
/// - `Ok(())`: The public key is valid
505-
/// - `Err(ErrorKind::InvalidPoint)`: The point is not on the curve
506-
/// - `Err(ErrorKind::WeakKey)`: The point is the identity element or otherwise weak
507-
fn validate(&self) -> Result<(), ErrorKind>;
515+
/// Optional serialization support for private keys.
516+
///
517+
/// This trait provides serialization capabilities for private keys that support it,
518+
/// such as software-based keys. Hardware-based keys (HSMs, secure enclaves, etc.)
519+
/// do not need to implement this trait.
520+
///
521+
/// # Security Note
522+
///
523+
/// Implementing this trait exposes private key material as bytes. Only implement
524+
/// this for software keys where serialization is appropriate and secure.
525+
///
526+
/// # Example
527+
///
528+
/// ```rust,ignore
529+
/// use openprot_hal_blocking::ecdsa::{PrivateKey, SerializablePrivateKey, Curve};
530+
/// use zerocopy::{IntoBytes, FromBytes};
531+
///
532+
/// #[derive(IntoBytes, FromBytes)]
533+
/// struct SoftwarePrivateKey([u8; 32]);
534+
///
535+
/// impl<C: Curve> PrivateKey<C> for SoftwarePrivateKey { /* ... */ }
536+
/// impl<C: Curve> SerializablePrivateKey<C> for SoftwarePrivateKey {}
537+
/// ```
538+
pub trait SerializablePrivateKey<C: Curve>: PrivateKey<C> + IntoBytes + FromBytes {}
508539

509-
/// Create a new public key from x and y coordinates without validation.
510-
///
511-
/// # Safety
512-
/// This method should only be used when the caller can guarantee that
513-
/// the coordinates represent a valid point on the curve. Use `from_coordinates`
514-
/// for safe construction with validation.
515-
fn new_unchecked(x: C::Scalar, y: C::Scalar) -> Self;
516-
}
540+
/// Optional serialization support for public keys.
541+
///
542+
/// This trait provides serialization capabilities for public keys that support it.
543+
/// Most public key implementations should implement this since public keys are
544+
/// not sensitive and often need to be transmitted or stored.
545+
///
546+
/// # Example
547+
///
548+
/// ```rust,ignore
549+
/// use openprot_hal_blocking::ecdsa::{PublicKey, SerializablePublicKey, Curve};
550+
/// use zerocopy::{IntoBytes, FromBytes};
551+
///
552+
/// #[derive(IntoBytes, FromBytes)]
553+
/// struct MyPublicKey { /* ... */ }
554+
///
555+
/// impl<C: Curve> PublicKey<C> for MyPublicKey { /* ... */ }
556+
/// impl<C: Curve> SerializablePublicKey<C> for MyPublicKey {}
557+
/// ```
558+
pub trait SerializablePublicKey<C: Curve>: PublicKey<C> + IntoBytes + FromBytes {}
559+
560+
/// Optional serialization support for signatures.
561+
///
562+
/// This trait provides serialization capabilities for signatures that support it.
563+
/// Most signature implementations should implement this since signatures need
564+
/// to be transmitted and verified.
565+
///
566+
/// # Example
567+
///
568+
/// ```rust,ignore
569+
/// use openprot_hal_blocking::ecdsa::{Signature, SerializableSignature, Curve};
570+
/// use zerocopy::{IntoBytes, FromBytes};
571+
///
572+
/// #[derive(IntoBytes, FromBytes)]
573+
/// struct MySignature { /* ... */ }
574+
///
575+
/// impl<C: Curve> Signature<C> for MySignature { /* ... */ }
576+
/// impl<C: Curve> SerializableSignature<C> for MySignature {}
577+
/// ```
578+
pub trait SerializableSignature<C: Curve>: Signature<C> + IntoBytes + FromBytes {}
517579

518580
/// Trait for ECDSA key generation over a specific elliptic curve.
519581
///

0 commit comments

Comments
 (0)