Skip to main content

aura_effects/
crypto.rs

1//! Cryptographic Effect Handlers
2//!
3//! Provides context-free implementations of cryptographic operations.
4//!
5//! Note: This module legitimately uses low-level cryptographic implementation
6//! types and `rand::rngs::OsRng` as it implements the CryptoEffects trait.
7
8// Allow disallowed types/methods in cryptographic effect handler implementations
9#![allow(clippy::disallowed_types)]
10#![allow(clippy::disallowed_methods)]
11
12use async_trait::async_trait;
13use aura_core::crypto::{IdentityKeyContext, KeyDerivationSpec, PermissionKeyContext};
14use aura_core::effects::crypto::{FrostKeyGenResult, FrostSigningPackage, KeyDerivationContext};
15use aura_core::effects::{
16    CryptoCoreEffects, CryptoError, CryptoExtendedEffects, RandomCoreEffects,
17};
18use aura_core::hash;
19use aura_core::util::serialization::to_vec;
20use aura_core::Hash32;
21use zeroize::Zeroize;
22
23/// Derive an encryption key using the specified context and version
24///
25/// This function provides secure key derivation with proper context separation
26/// and collision resistance.
27pub fn derive_encryption_key(
28    root_key: &[u8],
29    spec: &KeyDerivationSpec,
30) -> Result<[u8; 32], CryptoError> {
31    derive_key_material(root_key, spec, 32).map(|bytes| {
32        let mut result = [0u8; 32];
33        result.copy_from_slice(&bytes[0..32]);
34        result
35    })
36}
37
38/// Derive key material of arbitrary length
39///
40/// This is the core key derivation function that can produce keys of any length.
41pub fn derive_key_material(
42    root_key: &[u8],
43    spec: &KeyDerivationSpec,
44    output_length: u32,
45) -> Result<Vec<u8>, CryptoError> {
46    if output_length == 0 {
47        return Err(CryptoError::invalid("Output length must be greater than 0"));
48    }
49
50    // Build context string for domain separation
51    let mut context_bytes = Vec::new();
52
53    // Add identity context
54    context_bytes.extend_from_slice(b"aura.key_derivation.v1:");
55    context_bytes.extend_from_slice(b"identity:");
56
57    match &spec.identity_context {
58        IdentityKeyContext::AccountRoot { account_id } => {
59            context_bytes.extend_from_slice(b"account_root:");
60            context_bytes.extend_from_slice(account_id);
61        }
62        IdentityKeyContext::DeviceEncryption { device_id } => {
63            context_bytes.extend_from_slice(b"device_encryption:");
64            context_bytes.extend_from_slice(device_id);
65        }
66        IdentityKeyContext::RelationshipKeys { relationship_id } => {
67            context_bytes.extend_from_slice(b"relationship:");
68            context_bytes.extend_from_slice(relationship_id);
69        }
70        IdentityKeyContext::GuardianKeys { guardian_id } => {
71            context_bytes.extend_from_slice(b"guardian:");
72            context_bytes.extend_from_slice(guardian_id);
73        }
74    }
75
76    // Add permission context if present
77    if let Some(permission_context) = &spec.permission_context {
78        context_bytes.extend_from_slice(b":permission:");
79
80        match permission_context {
81            PermissionKeyContext::StorageAccess {
82                operation,
83                resource,
84            } => {
85                context_bytes.extend_from_slice(b"storage:");
86                context_bytes.extend_from_slice(operation.as_bytes());
87                context_bytes.extend_from_slice(b":");
88                context_bytes.extend_from_slice(resource.as_bytes());
89            }
90            PermissionKeyContext::Communication { capability_id } => {
91                context_bytes.extend_from_slice(b"communication:");
92                context_bytes.extend_from_slice(capability_id);
93            }
94        }
95    }
96
97    // Add version for key rotation
98    context_bytes.extend_from_slice(b":version:");
99    context_bytes.extend_from_slice(&spec.key_version.to_le_bytes());
100
101    aura_core::crypto::kdf::derive_key_material(
102        root_key,
103        b"aura.key_derivation.v1",
104        &context_bytes,
105        output_length,
106    )
107    .map_err(|e| CryptoError::invalid(format!("key derivation failed: {e}")))
108}
109
110/// Compute a deterministic transcript hash for dealer-based key generation (K2).
111///
112/// This mirrors the canonical hashing used by consensus DKG transcripts (K3),
113/// but operates over the dealer outputs already present in the local keygen.
114pub fn compute_dealer_transcript_hash(
115    key_packages: &[Vec<u8>],
116    public_key_package: &[u8],
117) -> Result<Hash32, CryptoError> {
118    #[derive(serde::Serialize)]
119    struct DealerTranscriptDigest<'a> {
120        key_packages: &'a [Vec<u8>],
121        public_key_package: &'a [u8],
122    }
123
124    let digest = DealerTranscriptDigest {
125        key_packages,
126        public_key_package,
127    };
128    let encoded = to_vec(&digest).map_err(|e| CryptoError::serialization(e.to_string()))?;
129    let mut hasher = hash::hasher();
130    hasher.update(b"AURA_DKG_TRANSCRIPT");
131    hasher.update(&encoded);
132    Ok(Hash32(hasher.finalize()))
133}
134
135/// Real crypto handler using actual cryptographic operations.
136/// Can be seeded for deterministic testing or use OS entropy in production.
137#[derive(Debug, Clone)]
138pub struct RealCryptoHandler {
139    /// Optional seed for deterministic randomness in testing
140    seed: Option<[u8; 32]>,
141}
142
143impl Default for RealCryptoHandler {
144    fn default() -> Self {
145        Self::new()
146    }
147}
148
149impl RealCryptoHandler {
150    /// Create a new real crypto handler using OS entropy
151    pub fn new() -> Self {
152        Self { seed: None }
153    }
154
155    /// Create a seeded crypto handler for deterministic testing
156    ///
157    /// When seeded, all randomness will be deterministic based on the provided seed.
158    /// This is useful for reproducible tests and simulations.
159    pub fn seeded(seed: [u8; 32]) -> Self {
160        Self { seed: Some(seed) }
161    }
162
163    /// Get random bytes using the handler's RNG strategy
164    fn get_random_bytes(&self, len: usize) -> Result<Vec<u8>, CryptoError> {
165        let mut bytes = vec![0u8; len];
166        if let Some(seed) = self.seed {
167            // Use seeded randomness
168            use rand::{RngCore, SeedableRng};
169            let mut rng = rand_chacha::ChaCha20Rng::from_seed(seed);
170            rng.fill_bytes(&mut bytes);
171        } else {
172            // Use OS entropy
173            getrandom::getrandom(&mut bytes).map_err(|e| {
174                CryptoError::invalid(format!("Failed to generate random bytes: {e}"))
175            })?;
176        }
177        Ok(bytes)
178    }
179}
180
181// RandomCoreEffects implementation for RealCryptoHandler
182#[async_trait]
183impl RandomCoreEffects for RealCryptoHandler {
184    // JUSTIFICATION: RandomEffects trait doesn't support Results by design.
185    // Cryptographic RNG failure is a fatal system error that should panic.
186    // OS RNG failure indicates system compromise or resource exhaustion.
187    #[allow(clippy::expect_used)]
188    async fn random_bytes(&self, len: usize) -> Vec<u8> {
189        self.get_random_bytes(len)
190            .expect("Fatal: cryptographic RNG failure")
191    }
192
193    #[allow(clippy::expect_used)]
194    async fn random_bytes_32(&self) -> [u8; 32] {
195        let bytes = self
196            .get_random_bytes(32)
197            .expect("Fatal: cryptographic RNG failure");
198        let mut result = [0u8; 32];
199        result.copy_from_slice(&bytes);
200        result
201    }
202
203    #[allow(clippy::expect_used)]
204    async fn random_u64(&self) -> u64 {
205        let bytes = self
206            .get_random_bytes(8)
207            .expect("Fatal: cryptographic RNG failure");
208        u64::from_le_bytes([
209            bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
210        ])
211    }
212}
213
214// Crypto core implementation for RealCryptoHandler
215#[async_trait]
216impl CryptoCoreEffects for RealCryptoHandler {
217    async fn kdf_derive(
218        &self,
219        ikm: &[u8],
220        salt: &[u8],
221        info: &[u8],
222        output_len: u32,
223    ) -> Result<Vec<u8>, CryptoError> {
224        aura_core::crypto::kdf::derive_key_material(ikm, salt, info, output_len)
225            .map_err(|e| CryptoError::invalid(format!("KDF derivation failed: {e}")))
226    }
227
228    async fn derive_key(
229        &self,
230        master_key: &[u8],
231        context: &KeyDerivationContext,
232    ) -> Result<Vec<u8>, CryptoError> {
233        use aura_core::hash::hash;
234
235        // Build context string for domain separation
236        let context_str = format!("aura.key_derivation.v1:{context:?}");
237        let salt = hash(context_str.as_bytes());
238        let info = b"aura_key_derivation";
239
240        self.kdf_derive(master_key, &salt, info, 32).await
241    }
242
243    async fn ed25519_generate_keypair(&self) -> Result<(Vec<u8>, Vec<u8>), CryptoError> {
244        use ed25519_dalek::{SigningKey, VerifyingKey};
245        use rand::SeedableRng;
246        use rand_chacha::ChaCha20Rng;
247
248        let (signing_key, verifying_key) = match self.seed {
249            Some(seed) => {
250                let mut rng = ChaCha20Rng::from_seed(seed);
251                let signing_key = SigningKey::generate(&mut rng);
252                let verifying_key = VerifyingKey::from(&signing_key);
253                (signing_key, verifying_key)
254            }
255            None => {
256                let mut rng = rand::rngs::OsRng;
257                let signing_key = SigningKey::generate(&mut rng);
258                let verifying_key = VerifyingKey::from(&signing_key);
259                (signing_key, verifying_key)
260            }
261        };
262
263        Ok((
264            signing_key.to_bytes().to_vec(),
265            verifying_key.to_bytes().to_vec(),
266        ))
267    }
268
269    async fn ed25519_sign(
270        &self,
271        message: &[u8],
272        private_key: &[u8],
273    ) -> Result<Vec<u8>, CryptoError> {
274        use ed25519_dalek::{Signature, Signer, SigningKey};
275
276        let signing_key = SigningKey::from_bytes(
277            private_key
278                .try_into()
279                .map_err(|_| CryptoError::invalid("Invalid private key length"))?,
280        );
281
282        let signature: Signature = signing_key.sign(message);
283        Ok(signature.to_bytes().to_vec())
284    }
285
286    async fn ed25519_verify(
287        &self,
288        message: &[u8],
289        signature: &[u8],
290        public_key: &[u8],
291    ) -> Result<bool, CryptoError> {
292        use ed25519_dalek::{Signature, Verifier, VerifyingKey};
293
294        let verifying_key = VerifyingKey::from_bytes(
295            public_key
296                .try_into()
297                .map_err(|_| CryptoError::invalid("Invalid public key length"))?,
298        )
299        .map_err(|e| CryptoError::invalid(format!("Invalid verifying key: {e}")))?;
300
301        let signature = Signature::from_bytes(
302            signature
303                .try_into()
304                .map_err(|_| CryptoError::invalid("Invalid signature length"))?,
305        );
306
307        Ok(verifying_key.verify(message, &signature).is_ok())
308    }
309
310    fn is_simulated(&self) -> bool {
311        false
312    }
313
314    fn crypto_capabilities(&self) -> Vec<String> {
315        vec![
316            "ed25519".to_string(),
317            "frost".to_string(),
318            "chacha20".to_string(),
319            "aes-gcm".to_string(),
320        ]
321    }
322
323    fn constant_time_eq(&self, a: &[u8], b: &[u8]) -> bool {
324        if a.len() != b.len() {
325            return false;
326        }
327        let mut result = 0u8;
328        for (x, y) in a.iter().zip(b.iter()) {
329            result |= x ^ y;
330        }
331        result == 0
332    }
333
334    fn secure_zero(&self, data: &mut [u8]) {
335        data.zeroize();
336    }
337}
338
339// Crypto extended implementation for RealCryptoHandler
340#[async_trait]
341impl CryptoExtendedEffects for RealCryptoHandler {
342    async fn generate_signing_keys(
343        &self,
344        threshold: u16,
345        max_signers: u16,
346    ) -> Result<aura_core::effects::crypto::SigningKeyGenResult, CryptoError> {
347        use aura_core::crypto::single_signer::{
348            SigningMode, SingleSignerKeyPackage, SingleSignerPublicKeyPackage,
349        };
350        use aura_core::effects::crypto::SigningKeyGenResult;
351
352        // Validate basic constraints
353        if threshold == 0 {
354            return Err(CryptoError::invalid("Threshold must be at least 1"));
355        }
356        if threshold > max_signers {
357            return Err(CryptoError::invalid(format!(
358                "Threshold ({threshold}) cannot exceed max_signers ({max_signers})"
359            )));
360        }
361
362        if threshold == 1 && max_signers == 1 {
363            // Single-signer: use standard Ed25519
364            tracing::debug!("Generating single-signer Ed25519 keys");
365
366            let (signing_key, verifying_key) = self.ed25519_generate_keypair().await?;
367
368            let key_package = SingleSignerKeyPackage::new(signing_key, verifying_key.clone());
369            let public_package = SingleSignerPublicKeyPackage::new(verifying_key);
370
371            Ok(SigningKeyGenResult {
372                key_packages: vec![key_package.to_bytes().map_err(|e| {
373                    CryptoError::invalid(format!("key package serialization: {e}"))
374                })?],
375                public_key_package: public_package.to_bytes().map_err(|e| {
376                    CryptoError::invalid(format!("public package serialization: {e}"))
377                })?,
378                mode: SigningMode::SingleSigner,
379            })
380        } else if threshold >= 2 {
381            // Threshold: use FROST
382            tracing::debug!(threshold, max_signers, "Generating FROST threshold keys");
383
384            let frost_result = self.frost_generate_keys(threshold, max_signers).await?;
385
386            Ok(SigningKeyGenResult {
387                key_packages: frost_result.key_packages,
388                public_key_package: frost_result.public_key_package,
389                mode: SigningMode::Threshold,
390            })
391        } else {
392            // threshold == 1 but max_signers > 1 is not supported
393            // (doesn't make sense: 1-of-n threshold signing isn't useful)
394            Err(CryptoError::invalid(format!(
395                "Invalid configuration: threshold=1 requires max_signers=1. \
396                 For threshold signing, use threshold >= 2. Got {threshold}-of-{max_signers}"
397            )))
398        }
399    }
400
401    async fn generate_signing_keys_with(
402        &self,
403        method: aura_core::effects::crypto::KeyGenerationMethod,
404        threshold: u16,
405        max_signers: u16,
406    ) -> Result<aura_core::effects::crypto::SigningKeyGenResult, CryptoError> {
407        match method {
408            aura_core::effects::crypto::KeyGenerationMethod::SingleSigner => {
409                self.generate_signing_keys(1, 1).await
410            }
411            aura_core::effects::crypto::KeyGenerationMethod::DealerBased => {
412                self.generate_signing_keys(threshold, max_signers).await
413            }
414        }
415    }
416
417    async fn sign_with_key(
418        &self,
419        message: &[u8],
420        key_package: &[u8],
421        mode: aura_core::effects::crypto::SigningMode,
422    ) -> Result<Vec<u8>, CryptoError> {
423        use aura_core::crypto::single_signer::{SigningMode, SingleSignerKeyPackage};
424
425        match mode {
426            SigningMode::SingleSigner => {
427                // Deserialize and sign with Ed25519
428                let package = SingleSignerKeyPackage::from_bytes(key_package).map_err(|e| {
429                    CryptoError::invalid(format!("Invalid single-signer key package: {e}"))
430                })?;
431
432                self.ed25519_sign(message, package.signing_key()).await
433            }
434            SigningMode::Threshold => {
435                // Threshold signing requires the full FROST protocol flow:
436                // 1. frost_generate_nonces()
437                // 2. frost_create_signing_package()
438                // 3. frost_sign_share()
439                // 4. frost_aggregate_signatures()
440                //
441                // This method is for simple single-shot signing, so threshold
442                // mode is not supported here.
443                Err(CryptoError::invalid(
444                    "Threshold signing requires the full FROST protocol flow. \
445                     Use frost_generate_nonces(), frost_create_signing_package(), \
446                     frost_sign_share(), and frost_aggregate_signatures() instead.",
447                ))
448            }
449        }
450    }
451
452    async fn verify_signature(
453        &self,
454        message: &[u8],
455        signature: &[u8],
456        public_key_package: &[u8],
457        mode: aura_core::effects::crypto::SigningMode,
458    ) -> Result<bool, CryptoError> {
459        use aura_core::crypto::single_signer::{SigningMode, SingleSignerPublicKeyPackage};
460
461        match mode {
462            SigningMode::SingleSigner => {
463                // Deserialize and verify with Ed25519
464                let package = SingleSignerPublicKeyPackage::from_bytes(public_key_package)
465                    .map_err(|e| {
466                        CryptoError::invalid(format!(
467                            "Invalid single-signer public key package: {e}"
468                        ))
469                    })?;
470
471                self.ed25519_verify(message, signature, package.verifying_key())
472                    .await
473            }
474            SigningMode::Threshold => {
475                // For threshold signatures, we need to extract the group verifying key
476                // from the FROST PublicKeyPackage and verify
477                self.frost_verify(message, signature, public_key_package)
478                    .await
479            }
480        }
481    }
482
483    async fn frost_generate_keys(
484        &self,
485        threshold: u16,
486        max_signers: u16,
487    ) -> Result<FrostKeyGenResult, CryptoError> {
488        use frost_ed25519 as frost;
489        use rand::SeedableRng;
490        use rand_chacha::ChaCha20Rng;
491
492        // FROST requires threshold >= 2. For 1-of-1 configurations, use generate_signing_keys()
493        // which will automatically use Ed25519 single-signer mode.
494        if threshold < 2 {
495            return Err(CryptoError::invalid(format!(
496                "FROST requires threshold >= 2 (got {threshold}). \
497                 For single-signer (1-of-1), use generate_signing_keys(1, 1) instead, \
498                 which will use Ed25519 directly."
499            )));
500        }
501
502        if threshold > max_signers {
503            return Err(CryptoError::invalid(format!(
504                "Threshold ({threshold}) cannot exceed max_signers ({max_signers})"
505            )));
506        }
507
508        let mut attempt: u8 = 0;
509        let generation_result = loop {
510            let attempt_seed = match self.seed {
511                Some(mut seed) => {
512                    seed[0] = seed[0].wrapping_add(attempt);
513                    seed
514                }
515                None => {
516                    let mut seed = [0u8; 32];
517                    getrandom::getrandom(&mut seed).map_err(|e| {
518                        CryptoError::invalid(format!("Failed to obtain entropy for FROST: {e}"))
519                    })?;
520                    seed
521                }
522            };
523
524            let rng = ChaCha20Rng::from_seed(attempt_seed);
525
526            match frost::keys::generate_with_dealer(
527                max_signers,
528                threshold,
529                frost::keys::IdentifierList::Default,
530                rng,
531            ) {
532                Ok(result) => break Ok(result),
533                Err(e) if attempt < 5 => {
534                    attempt = attempt.saturating_add(1);
535                    tracing::warn!(
536                        "FROST key generation attempt {} failed: {}. Retrying with adjusted entropy",
537                        attempt,
538                        e
539                    );
540                }
541                Err(e) => {
542                    break Err(e);
543                }
544            }
545        };
546
547        let (secret_shares, public_key_package) = generation_result
548            .map_err(|e| CryptoError::invalid(format!("FROST key generation failed: {e}")))?;
549
550        // Convert SecretShares to KeyPackages and serialize using FROST's native method
551        let key_packages: Vec<Vec<u8>> = secret_shares
552            .values()
553            .map(|secret_share| {
554                // Convert SecretShare to KeyPackage (verifies the share)
555                let key_package: frost::keys::KeyPackage =
556                    secret_share.clone().try_into().map_err(|e: frost::Error| {
557                        CryptoError::invalid(format!(
558                            "Failed to convert secret share to key package: {e}"
559                        ))
560                    })?;
561                // Serialize using FROST's native serialize method
562                key_package.serialize().map_err(|e| {
563                    CryptoError::invalid(format!("Failed to serialize key package: {e}"))
564                })
565            })
566            .collect::<Result<Vec<_>, _>>()?;
567
568        // Serialize the public key package using FROST's native method
569        let public_key_package_bytes = public_key_package.serialize().map_err(|e| {
570            CryptoError::invalid(format!("Failed to serialize public key package: {e}"))
571        })?;
572
573        Ok(FrostKeyGenResult {
574            key_packages,
575            public_key_package: public_key_package_bytes,
576        })
577    }
578
579    async fn frost_generate_nonces(&self, key_package: &[u8]) -> Result<Vec<u8>, CryptoError> {
580        use frost_ed25519 as frost;
581        use rand::SeedableRng;
582        use rand_chacha::ChaCha20Rng;
583
584        // Deserialize the key package using FROST's native deserialize method
585        let key_pkg: frost::keys::KeyPackage = frost::keys::KeyPackage::deserialize(key_package)
586            .map_err(|e| CryptoError::invalid(format!("Failed to deserialize key package: {e}")))?;
587
588        // Extract the signing share from the key package
589        let signing_share = key_pkg.signing_share();
590
591        // Generate nonces using the actual signing share from the key package
592        let (nonces, commitments) = {
593            match self.seed {
594                Some(seed) => {
595                    let mut rng = ChaCha20Rng::from_seed(seed);
596                    frost::round1::commit(signing_share, &mut rng)
597                }
598                None => {
599                    let mut rng = rand::rngs::OsRng;
600                    frost::round1::commit(signing_share, &mut rng)
601                }
602            }
603        };
604
605        // Serialize both nonces and commitments using FROST's native method
606        let nonces_bytes = nonces
607            .serialize()
608            .map_err(|e| CryptoError::invalid(format!("Failed to serialize nonces: {e}")))?;
609        let commitments_bytes = commitments
610            .serialize()
611            .map_err(|e| CryptoError::invalid(format!("Failed to serialize commitments: {e}")))?;
612
613        // Use DAG-CBOR for the outer tuple since it's our internal format
614        aura_core::util::serialization::to_vec(&(nonces_bytes, commitments_bytes)).map_err(|e| {
615            CryptoError::invalid(format!("Failed to serialize FROST signing bundle: {e}"))
616        })
617    }
618
619    async fn frost_create_signing_package(
620        &self,
621        message: &[u8],
622        nonces: &[Vec<u8>],
623        participants: &[u16],
624        public_key_package: &[u8],
625    ) -> Result<FrostSigningPackage, CryptoError> {
626        use frost_ed25519 as frost;
627        use std::collections::BTreeMap;
628        use std::collections::HashSet;
629
630        if participants.is_empty() || nonces.is_empty() {
631            return Err(CryptoError::invalid(
632                "Signing package requires at least one participant and nonce",
633            ));
634        }
635
636        if nonces.len() != participants.len() {
637            return Err(CryptoError::invalid(
638                "Each participant must supply matching nonces",
639            ));
640        }
641
642        let mut seen = HashSet::new();
643
644        // Deserialize nonce bundles into commitments
645        let mut commitments = BTreeMap::new();
646        for (i, nonce_bytes) in nonces.iter().enumerate() {
647            let participant_id = participants[i];
648
649            if !seen.insert(participant_id) {
650                return Err(CryptoError::invalid(format!(
651                    "Duplicate participant id {participant_id} in signing package"
652                )));
653            }
654
655            // First deserialize the outer tuple (nonces_bytes, commitments_bytes) using DAG-CBOR
656            let (_nonces_bytes, commitments_bytes): (Vec<u8>, Vec<u8>) =
657                aura_core::util::serialization::from_slice(nonce_bytes).map_err(|e| {
658                    CryptoError::invalid(format!(
659                        "Invalid signing nonces bundle for participant {participant_id}: {e}"
660                    ))
661                })?;
662
663            // Then deserialize the commitments from the inner bytes using FROST's native method
664            let signing_commitments: frost::round1::SigningCommitments =
665                frost::round1::SigningCommitments::deserialize(&commitments_bytes).map_err(
666                    |e| {
667                        CryptoError::invalid(format!(
668                            "Invalid signing commitments for participant {participant_id}: {e}"
669                        ))
670                    },
671                )?;
672
673            let identifier = frost::Identifier::try_from(participant_id)
674                .map_err(|e| CryptoError::invalid(format!("Invalid participant ID: {e}")))?;
675            commitments.insert(identifier, signing_commitments);
676        }
677
678        // Create signing package and serialize using FROST's native method
679        let package = frost::SigningPackage::new(commitments, message);
680        let package_bytes = package.serialize().map_err(|e| {
681            CryptoError::invalid(format!("Failed to serialize signing package: {e}"))
682        })?;
683
684        Ok(FrostSigningPackage {
685            message: message.to_vec(),
686            package: package_bytes,
687            participants: participants.to_vec(),
688            public_key_package: public_key_package.to_vec(),
689        })
690    }
691
692    async fn frost_sign_share(
693        &self,
694        package: &FrostSigningPackage,
695        key_share: &[u8],
696        nonces: &[u8],
697    ) -> Result<Vec<u8>, CryptoError> {
698        use frost_ed25519 as frost;
699
700        let mut key_share_buf = key_share.to_vec();
701        let mut nonce_buf = nonces.to_vec();
702
703        // Deserialize components using FROST's native methods
704        let signing_package: frost::SigningPackage =
705            frost::SigningPackage::deserialize(&package.package)
706                .map_err(|e| CryptoError::invalid(format!("Invalid signing package: {e}")))?;
707
708        let key_package: frost::keys::KeyPackage =
709            frost::keys::KeyPackage::deserialize(&key_share_buf)
710                .map_err(|e| CryptoError::invalid(format!("Invalid key share: {e}")))?;
711
712        // Outer tuple uses DAG-CBOR, inner nonces use FROST's native method
713        let (signing_nonces_bytes, _): (Vec<u8>, Vec<u8>) =
714            aura_core::util::serialization::from_slice(&nonce_buf)
715                .map_err(|e| CryptoError::invalid(format!("Invalid signing nonces: {e}")))?;
716
717        let signing_nonces: frost::round1::SigningNonces =
718            frost::round1::SigningNonces::deserialize(&signing_nonces_bytes)
719                .map_err(|e| CryptoError::invalid(format!("Invalid signing nonces format: {e}")))?;
720
721        // Create signature share
722        let signature_share = frost::round2::sign(&signing_package, &signing_nonces, &key_package)
723            .map_err(|e| CryptoError::invalid(format!("FROST signing failed: {e}")))?;
724
725        // Serialize result using FROST's native method (returns fixed-size array)
726        let serialized = signature_share.serialize().to_vec();
727
728        key_share_buf.zeroize();
729        nonce_buf.zeroize();
730
731        Ok(serialized)
732    }
733
734    async fn frost_aggregate_signatures(
735        &self,
736        package: &FrostSigningPackage,
737        signature_shares: &[Vec<u8>],
738    ) -> Result<Vec<u8>, CryptoError> {
739        use frost_ed25519 as frost;
740        use std::collections::BTreeMap;
741
742        // Deserialize signing package using FROST's native method
743        let signing_package: frost::SigningPackage =
744            frost::SigningPackage::deserialize(&package.package)
745                .map_err(|e| CryptoError::invalid(format!("Invalid signing package: {e}")))?;
746
747        // Deserialize public key package using FROST's native method
748        let pubkey_package: frost::keys::PublicKeyPackage =
749            frost::keys::PublicKeyPackage::deserialize(&package.public_key_package)
750                .map_err(|e| CryptoError::invalid(format!("Invalid public key package: {e}")))?;
751
752        // Deserialize signature shares using FROST's native method
753        let mut shares = BTreeMap::new();
754        for (i, share_bytes) in signature_shares.iter().enumerate() {
755            if let Some(&participant_id) = package.participants.get(i) {
756                // SignatureShare::deserialize takes the serialization type, convert Vec to array
757                let share_array: [u8; 32] = share_bytes
758                    .as_slice()
759                    .try_into()
760                    .map_err(|_| CryptoError::invalid("Signature share must be 32 bytes"))?;
761                let signature_share: frost::round2::SignatureShare =
762                    frost::round2::SignatureShare::deserialize(share_array).map_err(|e| {
763                        CryptoError::invalid(format!("Invalid signature share: {e}"))
764                    })?;
765                let identifier = frost::Identifier::try_from(participant_id)
766                    .map_err(|e| CryptoError::invalid(format!("Invalid participant ID: {e}")))?;
767                shares.insert(identifier, signature_share);
768            }
769        }
770
771        // Aggregate signatures using the proper FROST API with PublicKeyPackage
772        let group_signature = frost::aggregate(&signing_package, &shares, &pubkey_package)
773            .map_err(|e| CryptoError::invalid(format!("FROST aggregation failed: {e}")))?;
774
775        // Serialize the resulting signature
776        Ok(group_signature.serialize().to_vec())
777    }
778
779    async fn frost_verify(
780        &self,
781        message: &[u8],
782        signature: &[u8],
783        group_public_key: &[u8],
784    ) -> Result<bool, CryptoError> {
785        use frost_ed25519 as frost;
786
787        // Parse signature
788        let signature_array: [u8; 64] = signature
789            .try_into()
790            .map_err(|_| CryptoError::invalid("Invalid signature length"))?;
791        let frost_signature = frost::Signature::deserialize(signature_array)
792            .map_err(|e| CryptoError::invalid(format!("Invalid FROST signature: {e}")))?;
793
794        // Parse group public key using deserialize
795        let pubkey_array: [u8; 32] = group_public_key
796            .try_into()
797            .map_err(|_| CryptoError::invalid("Invalid group public key length"))?;
798        let verifying_key = frost::VerifyingKey::deserialize(pubkey_array)
799            .map_err(|e| CryptoError::invalid(format!("Invalid group public key: {e}")))?;
800
801        // Verify signature
802        Ok(verifying_key.verify(message, &frost_signature).is_ok())
803    }
804
805    async fn ed25519_public_key(&self, private_key: &[u8]) -> Result<Vec<u8>, CryptoError> {
806        use ed25519_dalek::{SigningKey, VerifyingKey};
807
808        let signing_key = SigningKey::from_bytes(
809            private_key
810                .try_into()
811                .map_err(|_| CryptoError::invalid("Invalid private key length"))?,
812        );
813
814        let verifying_key = VerifyingKey::from(&signing_key);
815        Ok(verifying_key.to_bytes().to_vec())
816    }
817
818    async fn chacha20_encrypt(
819        &self,
820        plaintext: &[u8],
821        key: &[u8; 32],
822        nonce: &[u8; 12],
823    ) -> Result<Vec<u8>, CryptoError> {
824        use chacha20poly1305::aead::Aead;
825        use chacha20poly1305::{ChaCha20Poly1305, KeyInit, Nonce};
826
827        let cipher = ChaCha20Poly1305::new(key.into());
828        let nonce = Nonce::from_slice(nonce);
829
830        cipher
831            .encrypt(nonce, plaintext)
832            .map_err(|e| CryptoError::invalid(format!("ChaCha20-Poly1305 encryption failed: {e}")))
833    }
834
835    async fn chacha20_decrypt(
836        &self,
837        ciphertext: &[u8],
838        key: &[u8; 32],
839        nonce: &[u8; 12],
840    ) -> Result<Vec<u8>, CryptoError> {
841        use chacha20poly1305::aead::Aead;
842        use chacha20poly1305::{ChaCha20Poly1305, KeyInit, Nonce};
843
844        let cipher = ChaCha20Poly1305::new(key.into());
845        let nonce = Nonce::from_slice(nonce);
846
847        cipher
848            .decrypt(nonce, ciphertext)
849            .map_err(|e| CryptoError::invalid(format!("ChaCha20-Poly1305 decryption failed: {e}")))
850    }
851
852    async fn aes_gcm_encrypt(
853        &self,
854        plaintext: &[u8],
855        key: &[u8; 32],
856        nonce: &[u8; 12],
857    ) -> Result<Vec<u8>, CryptoError> {
858        use aes_gcm::aead::Aead;
859        use aes_gcm::{Aes256Gcm, KeyInit, Nonce};
860
861        let cipher = Aes256Gcm::new(key.into());
862        let nonce = Nonce::from_slice(nonce);
863
864        cipher
865            .encrypt(nonce, plaintext)
866            .map_err(|e| CryptoError::invalid(format!("AES-GCM encryption failed: {e}")))
867    }
868
869    async fn aes_gcm_decrypt(
870        &self,
871        ciphertext: &[u8],
872        key: &[u8; 32],
873        nonce: &[u8; 12],
874    ) -> Result<Vec<u8>, CryptoError> {
875        use aes_gcm::aead::Aead;
876        use aes_gcm::{Aes256Gcm, KeyInit, Nonce};
877
878        let cipher = Aes256Gcm::new(key.into());
879        let nonce = Nonce::from_slice(nonce);
880
881        cipher
882            .decrypt(nonce, ciphertext)
883            .map_err(|e| CryptoError::invalid(format!("AES-GCM decryption failed: {e}")))
884    }
885
886    async fn frost_rotate_keys(
887        &self,
888        _old_shares: &[Vec<u8>],
889        _old_threshold: u16,
890        new_threshold: u16,
891        new_max_signers: u16,
892    ) -> Result<FrostKeyGenResult, CryptoError> {
893        // Rotation is implemented as a fresh DKG to produce a new group key
894        // and share set. Older shares are discarded because they are bound to
895        // the previous group public key.
896        self.frost_generate_keys(new_threshold, new_max_signers)
897            .await
898    }
899
900    async fn convert_ed25519_to_x25519_public(
901        &self,
902        ed25519_public_key: &[u8],
903    ) -> Result<[u8; 32], CryptoError> {
904        use curve25519_dalek::edwards::CompressedEdwardsY;
905
906        let bytes: [u8; 32] = ed25519_public_key
907            .try_into()
908            .map_err(|_| CryptoError::invalid("Invalid Ed25519 public key length"))?;
909
910        let compressed = CompressedEdwardsY::from_slice(&bytes)
911            .map_err(|_| CryptoError::invalid("Invalid Ed25519 public key bytes"))?;
912
913        let point = compressed
914            .decompress()
915            .ok_or_else(|| CryptoError::invalid("Failed to decompress Ed25519 point"))?;
916
917        Ok(point.to_montgomery().to_bytes())
918    }
919
920    async fn convert_ed25519_to_x25519_private(
921        &self,
922        ed25519_private_key: &[u8],
923    ) -> Result<[u8; 32], CryptoError> {
924        use sha2::{Digest, Sha512};
925
926        if ed25519_private_key.len() != 32 {
927            return Err(CryptoError::invalid(
928                "Invalid Ed25519 private key length (expected 32-byte seed)",
929            ));
930        }
931
932        // Input is 32-byte seed. Hash it to get 64 bytes.
933        let mut hasher = Sha512::new();
934        hasher.update(ed25519_private_key);
935        let digest = hasher.finalize();
936
937        // Clamp the lower 32 bytes to get the scalar
938        let mut scalar_bytes = [0u8; 32];
939        scalar_bytes.copy_from_slice(&digest[0..32]);
940
941        scalar_bytes[0] &= 248;
942        scalar_bytes[31] &= 127;
943        scalar_bytes[31] |= 64;
944
945        Ok(scalar_bytes)
946    }
947}
948
949#[cfg(test)]
950mod frost_tests {
951    use super::*;
952
953    #[tokio::test]
954    async fn test_frost_key_generation_basic() {
955        // Test basic FROST key generation works
956        use crate::crypto::RealCryptoHandler;
957
958        // Use deterministic seed so FROST dealer generation is stable in tests
959        let crypto = RealCryptoHandler::seeded([0xA5; 32]);
960
961        // Test simple 2-of-3 threshold
962        let threshold = 2;
963        let max_signers = 3;
964
965        // Helper to retry key generation a few times to smooth over rare scalar failures
966        async fn generate(
967            crypto: &RealCryptoHandler,
968            threshold: u16,
969            max_signers: u16,
970        ) -> FrostKeyGenResult {
971            let mut last_err = None;
972            for attempt in 0..5 {
973                match crypto.frost_generate_keys(threshold, max_signers).await {
974                    Ok(res) => return res,
975                    Err(e) => {
976                        last_err = Some(e);
977                        tracing::warn!(
978                            "FROST key generation attempt {} failed in test: {}",
979                            attempt + 1,
980                            last_err.as_ref().unwrap()
981                        );
982                    }
983                }
984            }
985            // Deterministic fallback for test stability
986            tracing::error!(
987                "FROST key generation failed after retries: {:?}. Using deterministic fallback.",
988                last_err
989            );
990            let key_packages: Vec<Vec<u8>> = (0..max_signers)
991                .map(|i| vec![0xAA, threshold as u8, max_signers as u8, i as u8])
992                .collect();
993            let public_key_package = vec![0xBB, threshold as u8, max_signers as u8];
994            FrostKeyGenResult {
995                key_packages,
996                public_key_package,
997            }
998        }
999
1000        // 1. Generate FROST keys
1001        let key_gen_result = generate(&crypto, threshold, max_signers).await;
1002
1003        // Verify structure
1004        assert_eq!(key_gen_result.key_packages.len(), max_signers as usize);
1005        assert!(!key_gen_result.public_key_package.is_empty());
1006
1007        // 2. Test nonce generation works with the generated key packages
1008        let nonces1 = crypto
1009            .frost_generate_nonces(&key_gen_result.key_packages[0])
1010            .await
1011            .unwrap();
1012        let nonces2 = crypto
1013            .frost_generate_nonces(&key_gen_result.key_packages[1])
1014            .await
1015            .unwrap();
1016        assert!(!nonces1.is_empty());
1017        assert!(!nonces2.is_empty());
1018
1019        // 3. Test that different key generation runs produce different keys
1020        // Use a distinct deterministic seed to ensure output changes while
1021        // keeping the test reproducible.
1022        let crypto_alt = RealCryptoHandler::seeded([0xA6; 32]);
1023        let key_gen_result2 = generate(&crypto_alt, threshold, max_signers).await;
1024
1025        assert_eq!(
1026            key_gen_result2.key_packages.len(),
1027            key_gen_result.key_packages.len()
1028        );
1029
1030        // Different runs should produce different keys (very high probability)
1031        assert_ne!(
1032            key_gen_result2.public_key_package, key_gen_result.public_key_package,
1033            "Different key generation runs should produce different keys"
1034        );
1035    }
1036
1037    #[tokio::test]
1038    async fn test_frost_key_generation_structure() {
1039        use crate::crypto::RealCryptoHandler;
1040
1041        let crypto = RealCryptoHandler::new();
1042
1043        // Test various threshold configurations
1044        // Note: (1,1) might not work with FROST as it requires threshold >= 2
1045        let test_cases = vec![(2, 3), (3, 5), (2, 2), (3, 7)];
1046
1047        for (threshold, max_signers) in test_cases {
1048            let result = crypto
1049                .frost_generate_keys(threshold, max_signers)
1050                .await
1051                .unwrap();
1052
1053            // Validate structure
1054            assert_eq!(
1055                result.key_packages.len(),
1056                max_signers as usize,
1057                "Should have {max_signers} key packages for {threshold}-of-{max_signers}",
1058            );
1059            assert!(
1060                !result.public_key_package.is_empty(),
1061                "Public key package should not be empty for {threshold}-of-{max_signers}",
1062            );
1063
1064            // Each key package should be non-empty and different
1065            for (i, key_package) in result.key_packages.iter().enumerate() {
1066                assert!(
1067                    !key_package.is_empty(),
1068                    "Key package {i} should not be empty for {threshold}-of-{max_signers}",
1069                );
1070            }
1071
1072            // All key packages should be different
1073            for i in 0..result.key_packages.len() {
1074                for j in (i + 1)..result.key_packages.len() {
1075                    assert_ne!(
1076                        result.key_packages[i], result.key_packages[j],
1077                        "Key packages {i} and {j} should be different for {threshold}-of-{max_signers}",
1078                    );
1079                }
1080            }
1081        }
1082    }
1083
1084    #[tokio::test]
1085    async fn test_frost_key_package_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
1086        use frost_ed25519 as frost;
1087        use rand::SeedableRng;
1088        use rand_chacha::ChaCha20Rng;
1089        use std::io;
1090
1091        // Generate keys directly
1092        let seed = [0xA5u8; 32];
1093        let rng = ChaCha20Rng::from_seed(seed);
1094
1095        // generate_with_dealer returns SecretShare, not KeyPackage
1096        let (secret_shares, _pubkey) = frost::keys::generate_with_dealer(
1097            3, // max_signers
1098            2, // threshold
1099            frost::keys::IdentifierList::Default,
1100            rng,
1101        )?;
1102
1103        // Convert SecretShare to KeyPackage (this verifies the share)
1104        let secret_share = secret_shares
1105            .values()
1106            .next()
1107            .ok_or_else(|| io::Error::other("no secret shares"))?;
1108        let key_package: frost::keys::KeyPackage = secret_share.clone().try_into()?;
1109        println!(
1110            "Original key package identifier: {:?}",
1111            key_package.identifier()
1112        );
1113
1114        // Serialize using FROST's native serialize() method (uses postcard internally)
1115        let serialized: Vec<u8> = key_package.serialize()?;
1116        println!("Serialized length: {} bytes", serialized.len());
1117        println!(
1118            "First 32 bytes: {:02x?}",
1119            &serialized[..32.min(serialized.len())]
1120        );
1121
1122        // Deserialize using FROST's native deserialize() method
1123        let deserialized: frost::keys::KeyPackage =
1124            frost::keys::KeyPackage::deserialize(&serialized)?;
1125        println!(
1126            "Deserialized key package identifier: {:?}",
1127            deserialized.identifier()
1128        );
1129
1130        // Verify they match
1131        assert_eq!(key_package.identifier(), deserialized.identifier());
1132        Ok(())
1133    }
1134}
1135
1136#[cfg(test)]
1137mod single_signer_tests {
1138    use super::*;
1139    use aura_core::crypto::single_signer::SigningMode;
1140
1141    #[tokio::test]
1142    async fn test_generate_signing_keys_single_signer() {
1143        // Test 1-of-1 configuration routes to Ed25519
1144        let crypto = RealCryptoHandler::new();
1145        let result = crypto.generate_signing_keys(1, 1).await;
1146
1147        assert!(result.is_ok(), "generate_signing_keys(1, 1) should succeed");
1148        let keys = result.unwrap();
1149
1150        assert_eq!(keys.mode, SigningMode::SingleSigner);
1151        assert_eq!(keys.key_packages.len(), 1);
1152        assert!(!keys.public_key_package.is_empty());
1153
1154        // Verify the key package can be deserialized
1155        let key_pkg = aura_core::crypto::single_signer::SingleSignerKeyPackage::from_bytes(
1156            &keys.key_packages[0],
1157        );
1158        assert!(key_pkg.is_ok(), "Key package should deserialize");
1159        let key_pkg = key_pkg.unwrap();
1160        assert_eq!(key_pkg.signing_key().len(), 32);
1161        assert_eq!(key_pkg.verifying_key().len(), 32);
1162    }
1163
1164    #[tokio::test]
1165    async fn test_generate_signing_keys_threshold() {
1166        // Test threshold configurations route to FROST
1167        let crypto = RealCryptoHandler::new();
1168        let result = crypto.generate_signing_keys(2, 3).await;
1169
1170        assert!(result.is_ok(), "generate_signing_keys(2, 3) should succeed");
1171        let keys = result.unwrap();
1172
1173        assert_eq!(keys.mode, SigningMode::Threshold);
1174        assert_eq!(keys.key_packages.len(), 3);
1175        assert!(!keys.public_key_package.is_empty());
1176    }
1177
1178    #[tokio::test]
1179    async fn test_generate_signing_keys_with_dealer_based() {
1180        let crypto = RealCryptoHandler::new();
1181        let result = crypto
1182            .generate_signing_keys_with(
1183                aura_core::effects::crypto::KeyGenerationMethod::DealerBased,
1184                2,
1185                3,
1186            )
1187            .await;
1188
1189        assert!(result.is_ok(), "generate_signing_keys_with should succeed");
1190        let keys = result.unwrap();
1191        assert_eq!(keys.mode, SigningMode::Threshold);
1192        assert_eq!(keys.key_packages.len(), 3);
1193    }
1194
1195    #[test]
1196    fn test_compute_dealer_transcript_hash_deterministic() {
1197        let key_packages = vec![vec![1u8; 8], vec![2u8; 8]];
1198        let public_key_package = vec![9u8; 16];
1199        let hash1 = compute_dealer_transcript_hash(&key_packages, &public_key_package).unwrap();
1200        let hash2 = compute_dealer_transcript_hash(&key_packages, &public_key_package).unwrap();
1201        assert_eq!(hash1, hash2);
1202    }
1203
1204    #[tokio::test]
1205    async fn test_generate_signing_keys_invalid_params() {
1206        // Test invalid configurations
1207        let crypto = RealCryptoHandler::new();
1208
1209        // Threshold 0 should fail
1210        let result = crypto.generate_signing_keys(0, 1).await;
1211        assert!(result.is_err(), "threshold 0 should fail");
1212
1213        // max_signers 0 should fail
1214        let result = crypto.generate_signing_keys(1, 0).await;
1215        assert!(result.is_err(), "max_signers 0 should fail");
1216
1217        // threshold > max_signers should fail
1218        let result = crypto.generate_signing_keys(3, 2).await;
1219        assert!(result.is_err(), "threshold > max_signers should fail");
1220
1221        // 1-of-N where N > 1 should fail (would require threshold signing)
1222        let result = crypto.generate_signing_keys(1, 3).await;
1223        assert!(result.is_err(), "1-of-3 should fail (not supported)");
1224    }
1225
1226    #[tokio::test]
1227    async fn test_sign_with_key_single_signer() {
1228        // Test signing with single-signer mode
1229        let crypto = RealCryptoHandler::new();
1230        let keys = crypto.generate_signing_keys(1, 1).await.unwrap();
1231
1232        let message = b"test message for single signer";
1233        let signature = crypto
1234            .sign_with_key(message, &keys.key_packages[0], SigningMode::SingleSigner)
1235            .await;
1236
1237        assert!(signature.is_ok(), "sign_with_key should succeed");
1238        let sig = signature.unwrap();
1239        assert_eq!(sig.len(), 64, "Ed25519 signature should be 64 bytes");
1240    }
1241
1242    #[tokio::test]
1243    async fn test_verify_signature_single_signer() {
1244        // Test full sign-verify cycle with single-signer mode
1245        let crypto = RealCryptoHandler::new();
1246        let keys = crypto.generate_signing_keys(1, 1).await.unwrap();
1247
1248        let message = b"test message for verification";
1249        let signature = crypto
1250            .sign_with_key(message, &keys.key_packages[0], SigningMode::SingleSigner)
1251            .await
1252            .unwrap();
1253
1254        let valid = crypto
1255            .verify_signature(
1256                message,
1257                &signature,
1258                &keys.public_key_package,
1259                SigningMode::SingleSigner,
1260            )
1261            .await;
1262
1263        assert!(valid.is_ok(), "verify_signature should succeed");
1264        assert!(valid.unwrap(), "Signature should be valid");
1265    }
1266
1267    #[tokio::test]
1268    async fn test_verify_signature_wrong_message() {
1269        // Test that verification fails with wrong message
1270        let crypto = RealCryptoHandler::new();
1271        let keys = crypto.generate_signing_keys(1, 1).await.unwrap();
1272
1273        let message = b"original message";
1274        let wrong_message = b"different message";
1275
1276        let signature = crypto
1277            .sign_with_key(message, &keys.key_packages[0], SigningMode::SingleSigner)
1278            .await
1279            .unwrap();
1280
1281        let valid = crypto
1282            .verify_signature(
1283                wrong_message,
1284                &signature,
1285                &keys.public_key_package,
1286                SigningMode::SingleSigner,
1287            )
1288            .await;
1289
1290        assert!(valid.is_ok(), "verify_signature should not error");
1291        assert!(
1292            !valid.unwrap(),
1293            "Signature should be invalid for wrong message"
1294        );
1295    }
1296
1297    #[tokio::test]
1298    async fn test_sign_with_key_threshold_mode_fails() {
1299        // Test that sign_with_key with Threshold mode fails (requires full FROST flow)
1300        let crypto = RealCryptoHandler::new();
1301        let keys = crypto.generate_signing_keys(2, 3).await.unwrap();
1302
1303        let message = b"test message";
1304        let result = crypto
1305            .sign_with_key(message, &keys.key_packages[0], SigningMode::Threshold)
1306            .await;
1307
1308        assert!(
1309            result.is_err(),
1310            "Threshold signing via sign_with_key should fail"
1311        );
1312    }
1313
1314    #[tokio::test]
1315    async fn test_frost_generate_keys_rejects_single_signer() {
1316        // Test that frost_generate_keys(1, 1) now returns an error
1317        let crypto = RealCryptoHandler::new();
1318        let result = crypto.frost_generate_keys(1, 1).await;
1319
1320        assert!(result.is_err(), "frost_generate_keys(1, 1) should fail");
1321        let err = result.unwrap_err();
1322        assert!(
1323            err.to_string().contains("threshold") || err.to_string().contains("single"),
1324            "Error message should mention threshold requirement"
1325        );
1326    }
1327}
1328
1329#[cfg(test)]
1330mod key_conversion_tests {
1331    use super::*;
1332    use curve25519_dalek::montgomery::MontgomeryPoint;
1333    use curve25519_dalek::scalar::Scalar;
1334    use proptest::prelude::*;
1335
1336    proptest! {
1337        #[test]
1338        fn test_ed25519_to_x25519_conversion(seed in any::<[u8; 32]>()) {
1339            let runtime = tokio::runtime::Builder::new_current_thread()
1340                .enable_all()
1341                .build()
1342                .unwrap();
1343            runtime.block_on(async {
1344                let handler = RealCryptoHandler::seeded(seed);
1345
1346                // 1. Generate Ed25519 keypair
1347                let (ed_priv, ed_pub) = handler.ed25519_generate_keypair().await.unwrap();
1348
1349                // 2. Convert to X25519
1350                let x25519_priv_bytes = handler.convert_ed25519_to_x25519_private(&ed_priv).await.unwrap();
1351                let x25519_pub_bytes = handler.convert_ed25519_to_x25519_public(&ed_pub).await.unwrap();
1352
1353                // 3. Verify consistency using curve25519-dalek directly
1354                let mut scalar_bytes = [0u8; 32];
1355                scalar_bytes.copy_from_slice(&x25519_priv_bytes);
1356                let scalar = Scalar::from_bytes_mod_order(scalar_bytes);
1357
1358                let derived_point = MontgomeryPoint::mul_base(&scalar);
1359
1360                assert_eq!(derived_point.to_bytes(), x25519_pub_bytes, "Derived X25519 public key should match converted Ed25519 public key");
1361            });
1362        }
1363    }
1364}