Skip to content

Commit c4d16c6

Browse files
committed
mls: fix msg decryption error due to missing exporter secret
Fixes #967 Pull-Request: #968 Signed-off-by: Yuki Kishimoto <yukikishimoto@protonmail.com>
1 parent 5a1aacb commit c4d16c6

File tree

3 files changed

+87
-45
lines changed

3 files changed

+87
-45
lines changed

mls/nostr-mls/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ pub mod groups;
2121
pub mod key_packages;
2222
pub mod messages;
2323
pub mod prelude;
24+
mod util;
2425
pub mod welcomes;
2526

2627
use self::constant::{DEFAULT_CIPHERSUITE, REQUIRED_EXTENSIONS};

mls/nostr-mls/src/messages.rs

Lines changed: 63 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use tls_codec::{Deserialize as TlsDeserialize, Serialize as TlsSerialize};
2020

2121
use crate::error::Error;
2222
use crate::prelude::*;
23-
use crate::NostrMls;
23+
use crate::{util, NostrMls};
2424

2525
/// Default number of epochs to look back when trying to decrypt messages with older exporter secrets
2626
const DEFAULT_EPOCH_LOOKBACK: u64 = 5;
@@ -495,7 +495,7 @@ where
495495
Ok(())
496496
}
497497

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
499499
///
500500
/// This helper method attempts to decrypt a message by trying exporter secrets from
501501
/// the most recent epoch backwards for a configurable number of epochs. This handles
@@ -504,25 +504,26 @@ where
504504
///
505505
/// # Arguments
506506
///
507-
/// * `group_id` - The MLS group ID
508-
/// * `current_epoch` - The current epoch of the group
507+
/// * `mls_group` - The MLS group
509508
/// * `encrypted_content` - The NIP-44 encrypted message content
510509
/// * `max_epoch_lookback` - Maximum number of epochs to search backwards (default: 5)
511510
///
512511
/// # Returns
513512
///
514513
/// * `Ok(Vec<u8>)` - The decrypted message bytes
515514
/// * `Err(Error)` - If decryption fails with all available exporter secrets
516-
fn try_decrypt_with_recent_epochs(
515+
fn try_decrypt_with_past_epochs(
517516
&self,
518-
group_id: &GroupId,
519-
current_epoch: u64,
517+
mls_group: &MlsGroup,
520518
encrypted_content: &str,
521519
max_epoch_lookback: u64,
522520
) -> Result<Vec<u8>, Error> {
521+
let group_id = mls_group.group_id();
522+
let current_epoch: u64 = mls_group.epoch().as_u64();
523+
523524
// 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);
526527

527528
for epoch in (end_epoch..=start_epoch).rev() {
528529
tracing::debug!(
@@ -538,34 +539,25 @@ where
538539
.get_group_exporter_secret(group_id, epoch)
539540
.map_err(|e| Error::Group(e.to_string()))
540541
{
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
569561
}
570562
}
571563
} else {
@@ -584,6 +576,38 @@ where
584576
)))
585577
}
586578

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+
587611
/// Processes an incoming encrypted Nostr event containing an MLS message
588612
///
589613
/// This is the main entry point for processing received messages. The function:
@@ -638,15 +662,9 @@ where
638662
.map_err(|e| Error::Group(e.to_string()))?
639663
.ok_or(Error::GroupNotFound)?;
640664

641-
let current_epoch = mls_group.epoch().as_u64();
642-
643665
// 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)?;
650668

651669
match self.process_message_for_group(&mut mls_group, &message_bytes) {
652670
Ok(ProcessedMessageContent::ApplicationMessage(application_message)) => {

mls/nostr-mls/src/util.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
use nostr::nips::nip44;
2+
use nostr::{Keys, SecretKey};
3+
use nostr_mls_storage::groups::types::GroupExporterSecret;
4+
5+
use crate::Error;
6+
7+
pub(crate) fn decrypt_with_exporter_secret(
8+
secret: &GroupExporterSecret,
9+
encrypted_content: &str,
10+
) -> Result<Vec<u8>, Error> {
11+
// Convert that secret to nostr keys
12+
let secret_key: SecretKey = SecretKey::from_slice(&secret.secret)?;
13+
let export_nostr_keys = Keys::new(secret_key);
14+
15+
// Decrypt message
16+
let message_bytes: Vec<u8> = nip44::decrypt_to_bytes(
17+
export_nostr_keys.secret_key(),
18+
&export_nostr_keys.public_key,
19+
encrypted_content,
20+
)?;
21+
22+
Ok(message_bytes)
23+
}

0 commit comments

Comments
 (0)