1#![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
23pub 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
38pub 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 let mut context_bytes = Vec::new();
52
53 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 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 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
110pub 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#[derive(Debug, Clone)]
138pub struct RealCryptoHandler {
139 seed: Option<[u8; 32]>,
141}
142
143impl Default for RealCryptoHandler {
144 fn default() -> Self {
145 Self::new()
146 }
147}
148
149impl RealCryptoHandler {
150 pub fn new() -> Self {
152 Self { seed: None }
153 }
154
155 pub fn seeded(seed: [u8; 32]) -> Self {
160 Self { seed: Some(seed) }
161 }
162
163 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 rand::{RngCore, SeedableRng};
169 let mut rng = rand_chacha::ChaCha20Rng::from_seed(seed);
170 rng.fill_bytes(&mut bytes);
171 } else {
172 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#[async_trait]
183impl RandomCoreEffects for RealCryptoHandler {
184 #[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#[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 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#[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 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 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 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 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 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 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 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 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 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 let key_packages: Vec<Vec<u8>> = secret_shares
552 .values()
553 .map(|secret_share| {
554 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 key_package.serialize().map_err(|e| {
563 CryptoError::invalid(format!("Failed to serialize key package: {e}"))
564 })
565 })
566 .collect::<Result<Vec<_>, _>>()?;
567
568 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 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 let signing_share = key_pkg.signing_share();
590
591 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 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 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 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 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 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 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 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 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 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 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 let signing_package: frost::SigningPackage =
744 frost::SigningPackage::deserialize(&package.package)
745 .map_err(|e| CryptoError::invalid(format!("Invalid signing package: {e}")))?;
746
747 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 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 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 let group_signature = frost::aggregate(&signing_package, &shares, &pubkey_package)
773 .map_err(|e| CryptoError::invalid(format!("FROST aggregation failed: {e}")))?;
774
775 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 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 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 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 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 let mut hasher = Sha512::new();
934 hasher.update(ed25519_private_key);
935 let digest = hasher.finalize();
936
937 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 use crate::crypto::RealCryptoHandler;
957
958 let crypto = RealCryptoHandler::seeded([0xA5; 32]);
960
961 let threshold = 2;
963 let max_signers = 3;
964
965 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 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 let key_gen_result = generate(&crypto, threshold, max_signers).await;
1002
1003 assert_eq!(key_gen_result.key_packages.len(), max_signers as usize);
1005 assert!(!key_gen_result.public_key_package.is_empty());
1006
1007 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 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 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 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 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 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 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 let seed = [0xA5u8; 32];
1093 let rng = ChaCha20Rng::from_seed(seed);
1094
1095 let (secret_shares, _pubkey) = frost::keys::generate_with_dealer(
1097 3, 2, frost::keys::IdentifierList::Default,
1100 rng,
1101 )?;
1102
1103 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 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 let deserialized: frost::keys::KeyPackage =
1124 frost::keys::KeyPackage::deserialize(&serialized)?;
1125 println!(
1126 "Deserialized key package identifier: {:?}",
1127 deserialized.identifier()
1128 );
1129
1130 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 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 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 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 let crypto = RealCryptoHandler::new();
1208
1209 let result = crypto.generate_signing_keys(0, 1).await;
1211 assert!(result.is_err(), "threshold 0 should fail");
1212
1213 let result = crypto.generate_signing_keys(1, 0).await;
1215 assert!(result.is_err(), "max_signers 0 should fail");
1216
1217 let result = crypto.generate_signing_keys(3, 2).await;
1219 assert!(result.is_err(), "threshold > max_signers should fail");
1220
1221 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 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 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 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 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 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 let (ed_priv, ed_pub) = handler.ed25519_generate_keypair().await.unwrap();
1348
1349 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 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}