Skip to main content

aura_agent/runtime/effects/
crypto.rs

1use super::AuraEffectSystem;
2use async_trait::async_trait;
3use aura_core::crypto::single_signer::SigningMode;
4use aura_core::effects::crypto::{
5    FrostKeyGenResult, FrostSigningPackage, KeyDerivationContext, KeyGenerationMethod,
6    SigningKeyGenResult,
7};
8use aura_core::effects::{
9    CryptoCoreEffects, CryptoError, CryptoExtendedEffects, RandomCoreEffects,
10    SecureStorageCapability, SecureStorageEffects, SecureStorageLocation,
11};
12use aura_core::{AuraError, AuthorityId};
13
14// Implementation of RandomCoreEffects
15#[async_trait]
16impl RandomCoreEffects for AuraEffectSystem {
17    #[allow(clippy::disallowed_methods)]
18    async fn random_bytes(&self, len: usize) -> Vec<u8> {
19        self.crypto.random_bytes(len)
20    }
21
22    #[allow(clippy::disallowed_methods)]
23    async fn random_bytes_32(&self) -> [u8; 32] {
24        self.crypto.random_32_bytes()
25    }
26
27    #[allow(clippy::disallowed_methods)]
28    async fn random_u64(&self) -> u64 {
29        self.crypto.random_u64()
30    }
31}
32
33// Implementation of CryptoCoreEffects
34#[async_trait]
35impl CryptoCoreEffects for AuraEffectSystem {
36    async fn kdf_derive(
37        &self,
38        ikm: &[u8],
39        salt: &[u8],
40        info: &[u8],
41        output_len: u32,
42    ) -> Result<Vec<u8>, CryptoError> {
43        self.crypto
44            .handler()
45            .kdf_derive(ikm, salt, info, output_len)
46            .await
47    }
48
49    async fn derive_key(
50        &self,
51        master_key: &[u8],
52        context: &KeyDerivationContext,
53    ) -> Result<Vec<u8>, CryptoError> {
54        self.crypto.handler().derive_key(master_key, context).await
55    }
56
57    async fn ed25519_generate_keypair(&self) -> Result<(Vec<u8>, Vec<u8>), CryptoError> {
58        self.crypto.handler().ed25519_generate_keypair().await
59    }
60
61    async fn ed25519_sign(
62        &self,
63        message: &[u8],
64        private_key: &[u8],
65    ) -> Result<Vec<u8>, CryptoError> {
66        self.crypto
67            .handler()
68            .ed25519_sign(message, private_key)
69            .await
70    }
71
72    async fn ed25519_verify(
73        &self,
74        message: &[u8],
75        signature: &[u8],
76        public_key: &[u8],
77    ) -> Result<bool, CryptoError> {
78        self.crypto
79            .handler()
80            .ed25519_verify(message, signature, public_key)
81            .await
82    }
83
84    fn is_simulated(&self) -> bool {
85        self.crypto.handler().is_simulated()
86    }
87
88    fn crypto_capabilities(&self) -> Vec<String> {
89        self.crypto.handler().crypto_capabilities()
90    }
91
92    fn constant_time_eq(&self, a: &[u8], b: &[u8]) -> bool {
93        self.crypto.handler().constant_time_eq(a, b)
94    }
95
96    fn secure_zero(&self, data: &mut [u8]) {
97        self.crypto.handler().secure_zero(data);
98    }
99}
100
101// Implementation of CryptoExtendedEffects
102#[async_trait]
103impl CryptoExtendedEffects for AuraEffectSystem {
104    async fn frost_generate_keys(
105        &self,
106        threshold: u16,
107        max_signers: u16,
108    ) -> Result<FrostKeyGenResult, CryptoError> {
109        self.crypto
110            .handler()
111            .frost_generate_keys(threshold, max_signers)
112            .await
113    }
114
115    async fn frost_generate_nonces(&self, key_package: &[u8]) -> Result<Vec<u8>, CryptoError> {
116        self.crypto
117            .handler()
118            .frost_generate_nonces(key_package)
119            .await
120    }
121
122    async fn frost_create_signing_package(
123        &self,
124        message: &[u8],
125        nonces: &[Vec<u8>],
126        participants: &[u16],
127        public_key_package: &[u8],
128    ) -> Result<FrostSigningPackage, CryptoError> {
129        self.crypto
130            .handler()
131            .frost_create_signing_package(message, nonces, participants, public_key_package)
132            .await
133    }
134
135    async fn frost_sign_share(
136        &self,
137        signing_package: &FrostSigningPackage,
138        key_share: &[u8],
139        nonces: &[u8],
140    ) -> Result<Vec<u8>, CryptoError> {
141        self.crypto
142            .handler()
143            .frost_sign_share(signing_package, key_share, nonces)
144            .await
145    }
146
147    async fn frost_aggregate_signatures(
148        &self,
149        signing_package: &FrostSigningPackage,
150        signature_shares: &[Vec<u8>],
151    ) -> Result<Vec<u8>, CryptoError> {
152        self.crypto
153            .handler()
154            .frost_aggregate_signatures(signing_package, signature_shares)
155            .await
156    }
157
158    async fn frost_verify(
159        &self,
160        message: &[u8],
161        signature: &[u8],
162        public_key: &[u8],
163    ) -> Result<bool, CryptoError> {
164        self.crypto
165            .handler()
166            .frost_verify(message, signature, public_key)
167            .await
168    }
169
170    async fn ed25519_public_key(&self, private_key: &[u8]) -> Result<Vec<u8>, CryptoError> {
171        self.crypto.handler().ed25519_public_key(private_key).await
172    }
173
174    async fn convert_ed25519_to_x25519_public(
175        &self,
176        ed25519_public_key: &[u8],
177    ) -> Result<[u8; 32], CryptoError> {
178        self.crypto
179            .handler()
180            .convert_ed25519_to_x25519_public(ed25519_public_key)
181            .await
182    }
183
184    async fn convert_ed25519_to_x25519_private(
185        &self,
186        ed25519_private_key: &[u8],
187    ) -> Result<[u8; 32], CryptoError> {
188        self.crypto
189            .handler()
190            .convert_ed25519_to_x25519_private(ed25519_private_key)
191            .await
192    }
193
194    async fn chacha20_encrypt(
195        &self,
196        plaintext: &[u8],
197        key: &[u8; 32],
198        nonce: &[u8; 12],
199    ) -> Result<Vec<u8>, CryptoError> {
200        self.crypto
201            .handler()
202            .chacha20_encrypt(plaintext, key, nonce)
203            .await
204    }
205
206    async fn chacha20_decrypt(
207        &self,
208        ciphertext: &[u8],
209        key: &[u8; 32],
210        nonce: &[u8; 12],
211    ) -> Result<Vec<u8>, CryptoError> {
212        self.crypto
213            .handler()
214            .chacha20_decrypt(ciphertext, key, nonce)
215            .await
216    }
217
218    async fn aes_gcm_encrypt(
219        &self,
220        plaintext: &[u8],
221        key: &[u8; 32],
222        nonce: &[u8; 12],
223    ) -> Result<Vec<u8>, CryptoError> {
224        self.crypto
225            .handler()
226            .aes_gcm_encrypt(plaintext, key, nonce)
227            .await
228    }
229
230    async fn aes_gcm_decrypt(
231        &self,
232        ciphertext: &[u8],
233        key: &[u8; 32],
234        nonce: &[u8; 12],
235    ) -> Result<Vec<u8>, CryptoError> {
236        self.crypto
237            .handler()
238            .aes_gcm_decrypt(ciphertext, key, nonce)
239            .await
240    }
241
242    async fn frost_rotate_keys(
243        &self,
244        old_shares: &[Vec<u8>],
245        old_threshold: u16,
246        new_threshold: u16,
247        new_max_signers: u16,
248    ) -> Result<FrostKeyGenResult, CryptoError> {
249        self.crypto
250            .handler()
251            .frost_rotate_keys(old_shares, old_threshold, new_threshold, new_max_signers)
252            .await
253    }
254
255    async fn generate_signing_keys(
256        &self,
257        threshold: u16,
258        max_signers: u16,
259    ) -> Result<SigningKeyGenResult, CryptoError> {
260        self.crypto
261            .handler()
262            .generate_signing_keys(threshold, max_signers)
263            .await
264    }
265
266    async fn generate_signing_keys_with(
267        &self,
268        method: KeyGenerationMethod,
269        threshold: u16,
270        max_signers: u16,
271    ) -> Result<SigningKeyGenResult, CryptoError> {
272        self.crypto
273            .handler()
274            .generate_signing_keys_with(method, threshold, max_signers)
275            .await
276    }
277
278    async fn sign_with_key(
279        &self,
280        message: &[u8],
281        key_package: &[u8],
282        mode: SigningMode,
283    ) -> Result<Vec<u8>, CryptoError> {
284        self.crypto
285            .handler()
286            .sign_with_key(message, key_package, mode)
287            .await
288    }
289
290    async fn verify_signature(
291        &self,
292        message: &[u8],
293        signature: &[u8],
294        public_key_package: &[u8],
295        mode: SigningMode,
296    ) -> Result<bool, CryptoError> {
297        self.crypto
298            .handler()
299            .verify_signature(message, signature, public_key_package, mode)
300            .await
301    }
302}
303
304// Implementation of ThresholdSigningEffects
305#[async_trait]
306impl aura_core::effects::ThresholdSigningEffects for AuraEffectSystem {
307    async fn bootstrap_authority(&self, authority: &AuthorityId) -> Result<Vec<u8>, AuraError> {
308        // Generate 1-of-1 signing keys (uses Ed25519 for single-signer mode)
309        let signing_keys = self.crypto.handler().generate_signing_keys(1, 1).await?;
310
311        // Store key package in secure storage
312        // Location varies by mode: signing_keys/ for Ed25519, frost_keys/ for FROST
313        let key_prefix = match signing_keys.mode {
314            SigningMode::SingleSigner => "signing_keys",
315            SigningMode::Threshold => "frost_keys",
316        };
317        let location = SecureStorageLocation::with_sub_key(
318            key_prefix,
319            format!("{}/0", authority), // epoch 0
320            "1",                        // signer index 1
321        );
322        let caps = vec![SecureStorageCapability::Write];
323        self.crypto
324            .secure_storage()
325            .secure_store(&location, &signing_keys.key_packages[0], &caps)
326            .await?;
327
328        // Store public key package
329        let pub_location = SecureStorageLocation::new(
330            format!("{}_public", key_prefix),
331            format!("{}/0", authority),
332        );
333        self.crypto
334            .secure_storage()
335            .secure_store(&pub_location, &signing_keys.public_key_package, &caps)
336            .await?;
337
338        // Store threshold config metadata for epoch 0 (bootstrap case: 1-of-1 single signer)
339        self.store_threshold_config_metadata(
340            authority,
341            0,   // epoch 0
342            1,   // threshold
343            1,   // total_participants
344            &[], // 1-of-1 bootstrap: participant set is implicit (local signer)
345            aura_core::threshold::AgreementMode::Provisional,
346        )
347        .await?;
348
349        // Bootstrap Biscuit authorization tokens
350        self.bootstrap_biscuit_tokens(authority).await?;
351
352        Ok(signing_keys.public_key_package)
353    }
354
355    async fn sign(
356        &self,
357        context: aura_core::threshold::SigningContext,
358    ) -> Result<aura_core::threshold::ThresholdSignature, AuraError> {
359        // Serialize the operation for signing
360        let message = serde_json::to_vec(&context.operation)
361            .map_err(|e| AuraError::internal(format!("Failed to serialize operation: {}", e)))?;
362
363        // Load key package from secure storage using tracked epoch
364        let current_epoch = self.get_current_epoch(&context.authority).await;
365        let location = SecureStorageLocation::with_sub_key(
366            "frost_keys",
367            format!("{}/{}", context.authority, current_epoch),
368            "1",
369        );
370        let caps = vec![SecureStorageCapability::Read];
371        let key_package = self
372            .crypto
373            .secure_storage()
374            .secure_retrieve(&location, &caps)
375            .await?;
376
377        // Load public key package for current epoch
378        let pub_location = SecureStorageLocation::new(
379            "frost_public_keys",
380            format!("{}/{}", context.authority, current_epoch),
381        );
382        let public_key_package = self
383            .crypto
384            .secure_storage()
385            .secure_retrieve(&pub_location, &caps)
386            .await
387            .unwrap_or_else(|_| vec![0u8; 32]); // Fallback for bootstrapped authorities
388
389        // Generate nonces
390        let nonces = self
391            .crypto
392            .handler()
393            .frost_generate_nonces(&key_package)
394            .await
395            .map_err(|e| AuraError::internal(format!("Nonce generation failed: {}", e)))?;
396
397        // Create signing package (single participant)
398        let participants = vec![1u16];
399        let signing_package = self
400            .crypto
401            .handler()
402            .frost_create_signing_package(
403                &message,
404                std::slice::from_ref(&nonces),
405                &participants,
406                &public_key_package,
407            )
408            .await
409            .map_err(|e| AuraError::internal(format!("Signing package creation failed: {}", e)))?;
410
411        // Sign
412        let share = self
413            .crypto
414            .handler()
415            .frost_sign_share(&signing_package, &key_package, &nonces)
416            .await
417            .map_err(|e| AuraError::internal(format!("Signature share creation failed: {}", e)))?;
418
419        // Aggregate (trivial for single signer)
420        let signature = self
421            .crypto
422            .handler()
423            .frost_aggregate_signatures(&signing_package, &[share])
424            .await
425            .map_err(|e| AuraError::internal(format!("Signature aggregation failed: {}", e)))?;
426
427        Ok(aura_core::threshold::ThresholdSignature::single_signer(
428            signature,
429            public_key_package,
430            current_epoch,
431        ))
432    }
433
434    async fn threshold_config(
435        &self,
436        authority: &AuthorityId,
437    ) -> Option<aura_core::threshold::ThresholdConfig> {
438        // Get current epoch for this authority
439        let current_epoch = self.get_current_epoch(authority).await;
440
441        // Retrieve stored threshold config metadata for this epoch
442        self.get_threshold_config_metadata(authority, current_epoch)
443            .await
444            .map(|metadata| aura_core::threshold::ThresholdConfig {
445                threshold: metadata.threshold_k,
446                total_participants: metadata.total_n,
447            })
448    }
449
450    async fn threshold_state(
451        &self,
452        authority: &AuthorityId,
453    ) -> Option<aura_core::threshold::ThresholdState> {
454        // Get current epoch for this authority
455        let current_epoch = self.get_current_epoch(authority).await;
456
457        // Retrieve stored threshold config metadata for this epoch
458        self.get_threshold_config_metadata(authority, current_epoch)
459            .await
460            .map(|metadata| aura_core::threshold::ThresholdState {
461                epoch: current_epoch,
462                threshold: metadata.threshold_k,
463                total_participants: metadata.total_n,
464                participants: metadata.resolved_participants(),
465                agreement_mode: metadata.agreement_mode,
466            })
467    }
468
469    async fn has_signing_capability(&self, authority: &AuthorityId) -> bool {
470        let current_epoch = self.get_current_epoch(authority).await;
471        let location = SecureStorageLocation::with_sub_key(
472            "frost_keys",
473            format!("{}/{}", authority, current_epoch),
474            "1",
475        );
476        self.crypto
477            .secure_storage()
478            .secure_exists(&location)
479            .await
480            .unwrap_or(false)
481    }
482
483    async fn public_key_package(&self, authority: &AuthorityId) -> Option<Vec<u8>> {
484        let location = SecureStorageLocation::new("frost_public_keys", format!("{}/0", authority));
485        let caps = vec![SecureStorageCapability::Read];
486        self.crypto
487            .secure_storage()
488            .secure_retrieve(&location, &caps)
489            .await
490            .ok()
491    }
492
493    async fn rotate_keys(
494        &self,
495        authority: &AuthorityId,
496        new_threshold: u16,
497        new_total_participants: u16,
498        participants: &[aura_core::threshold::ParticipantIdentity],
499    ) -> Result<(u64, Vec<Vec<u8>>, Vec<u8>), AuraError> {
500        tracing::info!(
501            ?authority,
502            new_threshold,
503            new_total_participants,
504            num_participants = participants.len(),
505            "Rotating threshold keys via AuraEffectSystem"
506        );
507
508        // Validate inputs
509        if participants.len() != new_total_participants as usize {
510            return Err(AuraError::invalid(format!(
511                "Participant count ({}) must match total_participants ({})",
512                participants.len(),
513                new_total_participants
514            )));
515        }
516
517        // Get current epoch and calculate new epoch
518        let current_epoch = self.get_current_epoch(authority).await;
519        let new_epoch = current_epoch + 1;
520        tracing::debug!(
521            ?authority,
522            current_epoch,
523            new_epoch,
524            "Rotating keys from epoch {} to {}",
525            current_epoch,
526            new_epoch
527        );
528
529        // Generate new threshold keys
530        let key_result = if new_threshold >= 2 {
531            self.crypto
532                .handler()
533                .frost_rotate_keys(&[], 0, new_threshold, new_total_participants)
534                .await?
535        } else {
536            let result = self
537                .crypto
538                .handler()
539                .generate_signing_keys(new_threshold, new_total_participants)
540                .await?;
541            FrostKeyGenResult {
542                key_packages: result.key_packages,
543                public_key_package: result.public_key_package,
544            }
545        };
546
547        // Store guardian key packages
548        let caps = vec![
549            SecureStorageCapability::Read,
550            SecureStorageCapability::Write,
551        ];
552        for (participant, key_package) in participants.iter().zip(key_result.key_packages.iter()) {
553            let location = SecureStorageLocation::with_sub_key(
554                "participant_shares",
555                format!("{}/{}", authority, new_epoch),
556                participant.storage_key(),
557            );
558            self.crypto
559                .secure_storage()
560                .secure_store(&location, key_package, &caps)
561                .await?;
562        }
563
564        // Store public key package
565        let pub_location = SecureStorageLocation::with_sub_key(
566            "threshold_pubkey",
567            format!("{}", authority),
568            format!("{}", new_epoch),
569        );
570        self.crypto
571            .secure_storage()
572            .secure_store(&pub_location, &key_result.public_key_package, &caps)
573            .await?;
574
575        // Store threshold config metadata for the new epoch
576        self.store_threshold_config_metadata(
577            authority,
578            new_epoch,
579            new_threshold,
580            new_total_participants,
581            participants,
582            aura_core::threshold::AgreementMode::CoordinatorSoftSafe,
583        )
584        .await?;
585
586        Ok((
587            new_epoch,
588            key_result.key_packages,
589            key_result.public_key_package,
590        ))
591    }
592
593    async fn commit_key_rotation(
594        &self,
595        authority: &AuthorityId,
596        new_epoch: u64,
597    ) -> Result<(), AuraError> {
598        tracing::info!(
599            ?authority,
600            new_epoch,
601            "Committing key rotation via AuraEffectSystem"
602        );
603        // Activate the new epoch by updating the current epoch state
604        self.set_current_epoch(authority, new_epoch).await?;
605        tracing::debug!(
606            ?authority,
607            new_epoch,
608            "Epoch state updated - new keys are now active"
609        );
610        Ok(())
611    }
612
613    async fn rollback_key_rotation(
614        &self,
615        authority: &AuthorityId,
616        failed_epoch: u64,
617    ) -> Result<(), AuraError> {
618        tracing::warn!(
619            ?authority,
620            failed_epoch,
621            "Rolling back key rotation via AuraEffectSystem"
622        );
623        // Delete orphaned keys from the failed epoch to prevent storage leakage
624        self.delete_epoch_keys(authority, failed_epoch).await?;
625        tracing::info!(
626            ?authority,
627            failed_epoch,
628            "Successfully deleted orphaned keys from failed rotation"
629        );
630        Ok(())
631    }
632}