Skip to main content

auths_crypto/
provider.rs

1//! Pluggable cryptographic abstraction for Ed25519 operations.
2//!
3//! Defines the [`CryptoProvider`] trait for Ed25519 verification, signing, and
4//! key generation — enabling `ring` on native targets and `WebCrypto` on WASM.
5
6use async_trait::async_trait;
7use zeroize::{Zeroize, ZeroizeOnDrop};
8
9/// Error type for cryptographic operations.
10///
11/// Usage:
12/// ```ignore
13/// match result {
14///     Err(CryptoError::InvalidSignature) => { /* signature did not verify */ }
15///     Err(CryptoError::UnsupportedTarget) => { /* not available on this platform */ }
16///     Err(CryptoError::OperationFailed(msg)) => { /* backend error */ }
17///     Ok(()) => { /* success */ }
18/// }
19/// ```
20#[derive(Debug, Clone, thiserror::Error)]
21#[non_exhaustive]
22pub enum CryptoError {
23    #[error("Invalid signature")]
24    InvalidSignature,
25
26    #[error("Invalid public key length: expected {expected}, got {actual}")]
27    InvalidKeyLength { expected: usize, actual: usize },
28
29    #[error("Invalid private key: {0}")]
30    InvalidPrivateKey(String),
31
32    #[error("Crypto operation failed: {0}")]
33    OperationFailed(String),
34
35    #[error("Operation not supported on current compilation target")]
36    UnsupportedTarget,
37}
38
39impl crate::AuthsErrorInfo for CryptoError {
40    fn error_code(&self) -> &'static str {
41        match self {
42            Self::InvalidSignature => "AUTHS-E1001",
43            Self::InvalidKeyLength { .. } => "AUTHS-E1002",
44            Self::InvalidPrivateKey(_) => "AUTHS-E1003",
45            Self::OperationFailed(_) => "AUTHS-E1004",
46            Self::UnsupportedTarget => "AUTHS-E1005",
47        }
48    }
49
50    fn suggestion(&self) -> Option<&'static str> {
51        match self {
52            Self::InvalidSignature => Some("The signature does not match the data or public key"),
53            Self::InvalidKeyLength { .. } => Some("Ensure the key is exactly 32 bytes for Ed25519"),
54            Self::UnsupportedTarget => {
55                Some("This operation is not available on the current platform")
56            }
57            _ => None,
58        }
59    }
60}
61
62/// Zeroize-on-drop wrapper for a raw 32-byte Ed25519 seed.
63///
64/// This is the portable key representation that crosses the [`CryptoProvider`]
65/// boundary. No ring types leak through the trait — only this raw seed.
66/// The provider materializes the internal keypair from the seed on each call.
67///
68/// Usage:
69/// ```ignore
70/// let (seed, pubkey) = provider.generate_ed25519_keypair().await?;
71/// let sig = provider.sign_ed25519(&seed, b"hello").await?;
72/// // seed is securely zeroed when dropped
73/// ```
74#[derive(Clone, Zeroize, ZeroizeOnDrop)]
75pub struct SecureSeed([u8; 32]);
76
77impl SecureSeed {
78    pub fn new(bytes: [u8; 32]) -> Self {
79        Self(bytes)
80    }
81
82    pub fn as_bytes(&self) -> &[u8; 32] {
83        &self.0
84    }
85}
86
87impl std::fmt::Debug for SecureSeed {
88    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89        f.write_str("SecureSeed([REDACTED])")
90    }
91}
92
93/// Abstraction for Ed25519 cryptographic operations across target architectures.
94///
95/// All method signatures use primitive Rust types or [`SecureSeed`] — no
96/// ring-specific types. This ensures domain crates (`auths-core`, `auths-sdk`)
97/// compile without any ring dependency.
98///
99/// Usage:
100/// ```ignore
101/// use auths_crypto::CryptoProvider;
102///
103/// async fn roundtrip(provider: &dyn CryptoProvider) {
104///     let (seed, pk) = provider.generate_ed25519_keypair().await.unwrap();
105///     let sig = provider.sign_ed25519(&seed, b"msg").await.unwrap();
106///     provider.verify_ed25519(&pk, b"msg", &sig).await.unwrap();
107/// }
108/// ```
109#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
110#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
111pub trait CryptoProvider: Send + Sync {
112    /// Verify an Ed25519 signature against a public key and message.
113    async fn verify_ed25519(
114        &self,
115        pubkey: &[u8],
116        message: &[u8],
117        signature: &[u8],
118    ) -> Result<(), CryptoError>;
119
120    /// Sign a message using a raw 32-byte Ed25519 seed.
121    ///
122    /// The provider materializes the internal keypair from the seed on each
123    /// call. This trades minor CPU overhead for a pure, ring-free domain layer.
124    ///
125    /// Args:
126    /// * `seed`: Raw 32-byte Ed25519 private key seed.
127    /// * `message`: The data to sign.
128    ///
129    /// Usage:
130    /// ```ignore
131    /// let sig = provider.sign_ed25519(&seed, b"hello").await?;
132    /// assert_eq!(sig.len(), 64);
133    /// ```
134    async fn sign_ed25519(&self, seed: &SecureSeed, message: &[u8])
135    -> Result<Vec<u8>, CryptoError>;
136
137    /// Generate a fresh Ed25519 keypair.
138    ///
139    /// Returns the raw 32-byte seed and 32-byte public key.
140    ///
141    /// Usage:
142    /// ```ignore
143    /// let (seed, pubkey) = provider.generate_ed25519_keypair().await?;
144    /// assert_eq!(pubkey.len(), 32);
145    /// ```
146    async fn generate_ed25519_keypair(&self) -> Result<(SecureSeed, [u8; 32]), CryptoError>;
147
148    /// Derive the 32-byte public key from a raw seed.
149    ///
150    /// Args:
151    /// * `seed`: Raw 32-byte Ed25519 private key seed.
152    ///
153    /// Usage:
154    /// ```ignore
155    /// let pk = provider.ed25519_public_key_from_seed(&seed).await?;
156    /// assert_eq!(pk.len(), 32);
157    /// ```
158    async fn ed25519_public_key_from_seed(
159        &self,
160        seed: &SecureSeed,
161    ) -> Result<[u8; 32], CryptoError>;
162}
163
164/// Ed25519 public key length in bytes.
165pub const ED25519_PUBLIC_KEY_LEN: usize = 32;
166
167/// Ed25519 signature length in bytes.
168pub const ED25519_SIGNATURE_LEN: usize = 64;