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)]
21pub enum CryptoError {
22 #[error("Invalid signature")]
23 InvalidSignature,
24
25 #[error("Invalid public key length: expected {expected}, got {actual}")]
26 InvalidKeyLength { expected: usize, actual: usize },
27
28 #[error("Invalid private key: {0}")]
29 InvalidPrivateKey(String),
30
31 #[error("Crypto operation failed: {0}")]
32 OperationFailed(String),
33
34 #[error("Operation not supported on current compilation target")]
35 UnsupportedTarget,
36}
37
38/// Zeroize-on-drop wrapper for a raw 32-byte Ed25519 seed.
39///
40/// This is the portable key representation that crosses the [`CryptoProvider`]
41/// boundary. No ring types leak through the trait — only this raw seed.
42/// The provider materializes the internal keypair from the seed on each call.
43///
44/// Usage:
45/// ```ignore
46/// let (seed, pubkey) = provider.generate_ed25519_keypair().await?;
47/// let sig = provider.sign_ed25519(&seed, b"hello").await?;
48/// // seed is securely zeroed when dropped
49/// ```
50#[derive(Clone, Zeroize, ZeroizeOnDrop)]
51pub struct SecureSeed([u8; 32]);
52
53impl SecureSeed {
54 pub fn new(bytes: [u8; 32]) -> Self {
55 Self(bytes)
56 }
57
58 pub fn as_bytes(&self) -> &[u8; 32] {
59 &self.0
60 }
61}
62
63impl std::fmt::Debug for SecureSeed {
64 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65 f.write_str("SecureSeed([REDACTED])")
66 }
67}
68
69/// Abstraction for Ed25519 cryptographic operations across target architectures.
70///
71/// All method signatures use primitive Rust types or [`SecureSeed`] — no
72/// ring-specific types. This ensures domain crates (`auths-core`, `auths-sdk`)
73/// compile without any ring dependency.
74///
75/// Usage:
76/// ```ignore
77/// use auths_crypto::CryptoProvider;
78///
79/// async fn roundtrip(provider: &dyn CryptoProvider) {
80/// let (seed, pk) = provider.generate_ed25519_keypair().await.unwrap();
81/// let sig = provider.sign_ed25519(&seed, b"msg").await.unwrap();
82/// provider.verify_ed25519(&pk, b"msg", &sig).await.unwrap();
83/// }
84/// ```
85#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
86#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
87pub trait CryptoProvider: Send + Sync {
88 /// Verify an Ed25519 signature against a public key and message.
89 async fn verify_ed25519(
90 &self,
91 pubkey: &[u8],
92 message: &[u8],
93 signature: &[u8],
94 ) -> Result<(), CryptoError>;
95
96 /// Sign a message using a raw 32-byte Ed25519 seed.
97 ///
98 /// The provider materializes the internal keypair from the seed on each
99 /// call. This trades minor CPU overhead for a pure, ring-free domain layer.
100 ///
101 /// Args:
102 /// * `seed`: Raw 32-byte Ed25519 private key seed.
103 /// * `message`: The data to sign.
104 ///
105 /// Usage:
106 /// ```ignore
107 /// let sig = provider.sign_ed25519(&seed, b"hello").await?;
108 /// assert_eq!(sig.len(), 64);
109 /// ```
110 async fn sign_ed25519(&self, seed: &SecureSeed, message: &[u8])
111 -> Result<Vec<u8>, CryptoError>;
112
113 /// Generate a fresh Ed25519 keypair.
114 ///
115 /// Returns the raw 32-byte seed and 32-byte public key.
116 ///
117 /// Usage:
118 /// ```ignore
119 /// let (seed, pubkey) = provider.generate_ed25519_keypair().await?;
120 /// assert_eq!(pubkey.len(), 32);
121 /// ```
122 async fn generate_ed25519_keypair(&self) -> Result<(SecureSeed, [u8; 32]), CryptoError>;
123
124 /// Derive the 32-byte public key from a raw seed.
125 ///
126 /// Args:
127 /// * `seed`: Raw 32-byte Ed25519 private key seed.
128 ///
129 /// Usage:
130 /// ```ignore
131 /// let pk = provider.ed25519_public_key_from_seed(&seed).await?;
132 /// assert_eq!(pk.len(), 32);
133 /// ```
134 async fn ed25519_public_key_from_seed(
135 &self,
136 seed: &SecureSeed,
137 ) -> Result<[u8; 32], CryptoError>;
138}
139
140/// Ed25519 public key length in bytes.
141pub const ED25519_PUBLIC_KEY_LEN: usize = 32;
142
143/// Ed25519 signature length in bytes.
144pub const ED25519_SIGNATURE_LEN: usize = 64;