Skip to main content

auths_crypto/
ring_provider.rs

1use async_trait::async_trait;
2
3use crate::provider::{CryptoError, CryptoProvider, ED25519_PUBLIC_KEY_LEN, SecureSeed};
4use ring::rand::SystemRandom;
5use ring::signature::{ED25519, Ed25519KeyPair, KeyPair, UnparsedPublicKey};
6
7/// Native Ed25519 provider powered by the `ring` crate.
8///
9/// Offloads CPU-bound operations to Tokio's blocking pool via
10/// `spawn_blocking` to prevent async reactor starvation under load.
11///
12/// Usage:
13/// ```ignore
14/// use auths_crypto::{CryptoProvider, RingCryptoProvider};
15///
16/// let provider = RingCryptoProvider;
17/// provider.verify_ed25519(&pubkey, &msg, &sig).await.unwrap();
18/// ```
19pub struct RingCryptoProvider;
20
21#[async_trait]
22impl CryptoProvider for RingCryptoProvider {
23    async fn verify_ed25519(
24        &self,
25        pubkey: &[u8],
26        message: &[u8],
27        signature: &[u8],
28    ) -> Result<(), CryptoError> {
29        if pubkey.len() != ED25519_PUBLIC_KEY_LEN {
30            return Err(CryptoError::InvalidKeyLength {
31                expected: ED25519_PUBLIC_KEY_LEN,
32                actual: pubkey.len(),
33            });
34        }
35
36        let pubkey = pubkey.to_vec();
37        let message = message.to_vec();
38        let signature = signature.to_vec();
39
40        tokio::task::spawn_blocking(move || {
41            let peer_public_key = UnparsedPublicKey::new(&ED25519, &pubkey);
42            peer_public_key
43                .verify(&message, &signature)
44                .map_err(|_| CryptoError::InvalidSignature)
45        })
46        .await
47        .map_err(|_| CryptoError::OperationFailed("Verification task panicked".into()))?
48    }
49
50    async fn sign_ed25519(
51        &self,
52        seed: &SecureSeed,
53        message: &[u8],
54    ) -> Result<Vec<u8>, CryptoError> {
55        let seed_bytes = *seed.as_bytes();
56        let message = message.to_vec();
57
58        // Keypair is re-materialized from the raw seed on each call.
59        // This trades minor CPU overhead for a pure, ring-free domain layer.
60        tokio::task::spawn_blocking(move || {
61            let keypair = Ed25519KeyPair::from_seed_unchecked(&seed_bytes)
62                .map_err(|e| CryptoError::InvalidPrivateKey(format!("{e}")))?;
63            Ok(keypair.sign(&message).as_ref().to_vec())
64        })
65        .await
66        .map_err(|_| CryptoError::OperationFailed("Signing task panicked".into()))?
67    }
68
69    async fn generate_ed25519_keypair(&self) -> Result<(SecureSeed, [u8; 32]), CryptoError> {
70        tokio::task::spawn_blocking(move || {
71            let rng = SystemRandom::new();
72            let pkcs8_doc = Ed25519KeyPair::generate_pkcs8(&rng)
73                .map_err(|_| CryptoError::OperationFailed("Key generation failed".into()))?;
74            let keypair = Ed25519KeyPair::from_pkcs8(pkcs8_doc.as_ref())
75                .map_err(|e| CryptoError::OperationFailed(format!("Parse generated key: {e}")))?;
76
77            let public_key: [u8; 32] = keypair
78                .public_key()
79                .as_ref()
80                .try_into()
81                .map_err(|_| CryptoError::OperationFailed("Public key not 32 bytes".into()))?;
82
83            // Extract the raw 32-byte seed from the PKCS#8 DER encoding.
84            // Ring's Ed25519 PKCS#8 v2 places the seed at bytes [16..48].
85            let pkcs8_bytes = pkcs8_doc.as_ref();
86            let seed: [u8; 32] = pkcs8_bytes[16..48]
87                .try_into()
88                .map_err(|_| CryptoError::OperationFailed("Seed extraction failed".into()))?;
89
90            Ok((SecureSeed::new(seed), public_key))
91        })
92        .await
93        .map_err(|_| CryptoError::OperationFailed("Keygen task panicked".into()))?
94    }
95
96    async fn ed25519_public_key_from_seed(
97        &self,
98        seed: &SecureSeed,
99    ) -> Result<[u8; 32], CryptoError> {
100        let seed_bytes = *seed.as_bytes();
101
102        tokio::task::spawn_blocking(move || {
103            let keypair = Ed25519KeyPair::from_seed_unchecked(&seed_bytes)
104                .map_err(|e| CryptoError::InvalidPrivateKey(format!("{e}")))?;
105            keypair
106                .public_key()
107                .as_ref()
108                .try_into()
109                .map_err(|_| CryptoError::OperationFailed("Public key not 32 bytes".into()))
110        })
111        .await
112        .map_err(|_| CryptoError::OperationFailed("Public key extraction panicked".into()))?
113    }
114}