@@ -20,7 +20,7 @@ use tls_codec::{Deserialize as TlsDeserialize, Serialize as TlsSerialize};
20
20
21
21
use crate :: error:: Error ;
22
22
use crate :: prelude:: * ;
23
- use crate :: NostrMls ;
23
+ use crate :: { util , NostrMls } ;
24
24
25
25
/// Default number of epochs to look back when trying to decrypt messages with older exporter secrets
26
26
const DEFAULT_EPOCH_LOOKBACK : u64 = 5 ;
@@ -495,7 +495,7 @@ where
495
495
Ok ( ( ) )
496
496
}
497
497
498
- /// Tries to decrypt a message using exporter secrets from multiple recent epochs
498
+ /// Tries to decrypt a message using exporter secrets from multiple recent epochs excluding the current one
499
499
///
500
500
/// This helper method attempts to decrypt a message by trying exporter secrets from
501
501
/// the most recent epoch backwards for a configurable number of epochs. This handles
@@ -504,25 +504,26 @@ where
504
504
///
505
505
/// # Arguments
506
506
///
507
- /// * `group_id` - The MLS group ID
508
- /// * `current_epoch` - The current epoch of the group
507
+ /// * `mls_group` - The MLS group
509
508
/// * `encrypted_content` - The NIP-44 encrypted message content
510
509
/// * `max_epoch_lookback` - Maximum number of epochs to search backwards (default: 5)
511
510
///
512
511
/// # Returns
513
512
///
514
513
/// * `Ok(Vec<u8>)` - The decrypted message bytes
515
514
/// * `Err(Error)` - If decryption fails with all available exporter secrets
516
- fn try_decrypt_with_recent_epochs (
515
+ fn try_decrypt_with_past_epochs (
517
516
& self ,
518
- group_id : & GroupId ,
519
- current_epoch : u64 ,
517
+ mls_group : & MlsGroup ,
520
518
encrypted_content : & str ,
521
519
max_epoch_lookback : u64 ,
522
520
) -> Result < Vec < u8 > , Error > {
521
+ let group_id = mls_group. group_id ( ) ;
522
+ let current_epoch: u64 = mls_group. epoch ( ) . as_u64 ( ) ;
523
+
523
524
// Start from current epoch and go backwards
524
- let start_epoch = current_epoch;
525
- let end_epoch = current_epoch . saturating_sub ( max_epoch_lookback) ;
525
+ let start_epoch: u64 = current_epoch. saturating_sub ( 1 ) ;
526
+ let end_epoch: u64 = start_epoch . saturating_sub ( max_epoch_lookback) ;
526
527
527
528
for epoch in ( end_epoch..=start_epoch) . rev ( ) {
528
529
tracing:: debug!(
@@ -538,34 +539,25 @@ where
538
539
. get_group_exporter_secret ( group_id, epoch)
539
540
. map_err ( |e| Error :: Group ( e. to_string ( ) ) )
540
541
{
541
- // Convert secret to nostr keys
542
- if let Ok ( secret_key) = SecretKey :: from_slice ( & secret. secret ) {
543
- let export_nostr_keys = Keys :: new ( secret_key) ;
544
-
545
- // Try to decrypt with this epoch's secret
546
- match nip44:: decrypt_to_bytes (
547
- export_nostr_keys. secret_key ( ) ,
548
- & export_nostr_keys. public_key ,
549
- encrypted_content,
550
- ) {
551
- Ok ( decrypted_bytes) => {
552
- tracing:: debug!(
553
- target: "nostr_mls::messages::try_decrypt_with_recent_epochs" ,
554
- "Successfully decrypted message with epoch {} for group {:?}" ,
555
- epoch,
556
- group_id
557
- ) ;
558
- return Ok ( decrypted_bytes) ;
559
- }
560
- Err ( e) => {
561
- tracing:: trace!(
562
- target: "nostr_mls::messages::try_decrypt_with_recent_epochs" ,
563
- "Failed to decrypt with epoch {}: {:?}" ,
564
- epoch,
565
- e
566
- ) ;
567
- // Continue to next epoch
568
- }
542
+ // Try to decrypt with this epoch's secret
543
+ match util:: decrypt_with_exporter_secret ( & secret, encrypted_content) {
544
+ Ok ( decrypted_bytes) => {
545
+ tracing:: debug!(
546
+ target: "nostr_mls::messages::try_decrypt_with_recent_epochs" ,
547
+ "Successfully decrypted message with epoch {} for group {:?}" ,
548
+ epoch,
549
+ group_id
550
+ ) ;
551
+ return Ok ( decrypted_bytes) ;
552
+ }
553
+ Err ( e) => {
554
+ tracing:: trace!(
555
+ target: "nostr_mls::messages::try_decrypt_with_recent_epochs" ,
556
+ "Failed to decrypt with epoch {}: {:?}" ,
557
+ epoch,
558
+ e
559
+ ) ;
560
+ // Continue to next epoch
569
561
}
570
562
}
571
563
} else {
@@ -584,6 +576,38 @@ where
584
576
) ) )
585
577
}
586
578
579
+ /// Try to decrypt using the current exporter secret and if fails try with the past ones until a max loopback of [`DEFAULT_EPOCH_LOOKBACK`].
580
+ fn try_decrypt_with_recent_epochs (
581
+ & self ,
582
+ mls_group : & MlsGroup ,
583
+ encrypted_content : & str ,
584
+ ) -> Result < Vec < u8 > , Error > {
585
+ // Get exporter secret for current epoch
586
+ let secret = self . exporter_secret ( mls_group. group_id ( ) ) ?;
587
+
588
+ // Try to decrypt it for the current epoch
589
+ match util:: decrypt_with_exporter_secret ( & secret, encrypted_content) {
590
+ Ok ( decrypted_bytes) => {
591
+ tracing:: debug!(
592
+ "Successfully decrypted message with current exporter secret for group {:?}" ,
593
+ mls_group. group_id( )
594
+ ) ;
595
+ Ok ( decrypted_bytes)
596
+ }
597
+ // Decryption failed using the current epoch exporter secret
598
+ Err ( _) => {
599
+ tracing:: debug!( "Failed to decrypt message with current exporter secret. Trying with past ones." ) ;
600
+
601
+ // Try with past exporter secrets
602
+ self . try_decrypt_with_past_epochs (
603
+ mls_group,
604
+ encrypted_content,
605
+ DEFAULT_EPOCH_LOOKBACK ,
606
+ )
607
+ }
608
+ }
609
+ }
610
+
587
611
/// Processes an incoming encrypted Nostr event containing an MLS message
588
612
///
589
613
/// This is the main entry point for processing received messages. The function:
@@ -638,15 +662,9 @@ where
638
662
. map_err ( |e| Error :: Group ( e. to_string ( ) ) ) ?
639
663
. ok_or ( Error :: GroupNotFound ) ?;
640
664
641
- let current_epoch = mls_group. epoch ( ) . as_u64 ( ) ;
642
-
643
665
// Try to decrypt message with recent exporter secrets (fallback across epochs)
644
- let message_bytes: Vec < u8 > = self . try_decrypt_with_recent_epochs (
645
- & group. mls_group_id ,
646
- current_epoch,
647
- & event. content ,
648
- DEFAULT_EPOCH_LOOKBACK ,
649
- ) ?;
666
+ let message_bytes: Vec < u8 > =
667
+ self . try_decrypt_with_recent_epochs ( & mls_group, & event. content ) ?;
650
668
651
669
match self . process_message_for_group ( & mut mls_group, & message_bytes) {
652
670
Ok ( ProcessedMessageContent :: ApplicationMessage ( application_message) ) => {
0 commit comments