Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ openmls_basic_credential = { git = "https://github.com/openmls/openmls", rev = "
openmls_rust_crypto = { git = "https://github.com/openmls/openmls", rev = "b90ca23b1238d9e189142d06234527b2d344d748", default-features = false }

# Nostr
nostr = { version = "0.43", default-features = false }
nostr = { version = "0.44", default-features = false }

# Serialization
serde = { version = "1.0", default-features = false }
Expand Down
3 changes: 2 additions & 1 deletion crates/mdk-core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

### Removed

### Deprecated
### Deprecated -->

## Unreleased

Expand All @@ -35,6 +35,7 @@
- When multiple valid commits are published for the same epoch, clients converge on the same "winning" commit.
- If a "better" commit (earlier timestamp) arrives after a "worse" commit has been applied, the client automatically rolls back to the previous epoch and applies the winning commit.
- This ensures consistent group state across all clients even with out-of-order message delivery.
- Upgraded `nostr` dependency from 0.43 to 0.44, replacing deprecated `Timestamp::as_u64()` calls with `Timestamp::as_secs()` ([#162](https://github.com/marmot-protocol/mdk/pull/162))

### Added

Expand Down
28 changes: 14 additions & 14 deletions crates/mdk-core/src/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -971,7 +971,7 @@ where
&group_id,
current_epoch,
&event.id,
event.created_at.as_u64(),
event.created_at.as_secs(),
) {
tracing::warn!(
target: "mdk_core::messages::process_commit_message_for_group",
Expand Down Expand Up @@ -1155,24 +1155,24 @@ where
let now = Timestamp::now();

// Reject events from the future (allow configurable clock skew)
if event.created_at.as_u64()
if event.created_at.as_secs()
> now
.as_u64()
.as_secs()
.saturating_add(self.config.max_future_skew_secs)
{
return Err(Error::InvalidTimestamp(format!(
"event timestamp {} is too far in the future (current time: {})",
event.created_at.as_u64(),
now.as_u64()
event.created_at.as_secs(),
now.as_secs()
)));
}

// Reject events that are too old (configurable via MdkConfig)
let min_timestamp = now.as_u64().saturating_sub(self.config.max_event_age_secs);
if event.created_at.as_u64() < min_timestamp {
let min_timestamp = now.as_secs().saturating_sub(self.config.max_event_age_secs);
if event.created_at.as_secs() < min_timestamp {
return Err(Error::InvalidTimestamp(format!(
"event timestamp {} is too old (minimum acceptable: {})",
event.created_at.as_u64(),
event.created_at.as_secs(),
min_timestamp
)));
}
Expand Down Expand Up @@ -1401,7 +1401,7 @@ where
&group.mls_group_id,
mls_group.epoch().as_u64(),
&event.id,
event.created_at.as_u64(),
event.created_at.as_secs(),
)
.is_err()
{
Expand Down Expand Up @@ -1630,7 +1630,7 @@ where
self.storage(),
&group.mls_group_id,
msg_epoch,
event.created_at.as_u64(),
event.created_at.as_secs(),
&event.id,
);

Expand Down Expand Up @@ -3165,9 +3165,9 @@ mod tests {
);

// Allow for some clock skew, but message shouldn't be more than a day old
let one_day_ago = now.as_u64().saturating_sub(86400);
let one_day_ago = now.as_secs().saturating_sub(86400);
assert!(
message_event.created_at.as_u64() > one_day_ago,
message_event.created_at.as_secs() > one_day_ago,
"Message timestamp should be recent"
);
}
Expand Down Expand Up @@ -6562,7 +6562,7 @@ mod tests {
.expect("Group should exist");

// Set timestamp to far future (1 hour ahead, beyond 5 minute skew allowance)
let future_time = nostr::Timestamp::now().as_u64() + 3600;
let future_time = nostr::Timestamp::now().as_secs() + 3600;

// Create an event with future timestamp
let message_event = EventBuilder::new(Kind::MlsGroupMessage, "test content")
Expand Down Expand Up @@ -6597,7 +6597,7 @@ mod tests {
.expect("Group should exist");

// Set timestamp to 46 days ago (beyond 45 day limit)
let old_time = nostr::Timestamp::now().as_u64().saturating_sub(46 * 86400);
let old_time = nostr::Timestamp::now().as_secs().saturating_sub(46 * 86400);

// Create an event with old timestamp
let message_event = EventBuilder::new(Kind::MlsGroupMessage, "test content")
Expand Down
2 changes: 1 addition & 1 deletion crates/mdk-core/src/test_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ impl RaceConditionSimulator {

/// Get a timestamp offset from the base by the specified number of seconds
pub fn timestamp_offset(&self, offset_seconds: i64) -> nostr::Timestamp {
let new_timestamp = (self.base_timestamp.as_u64() as i64 + offset_seconds).max(0) as u64;
let new_timestamp = (self.base_timestamp.as_secs() as i64 + offset_seconds).max(0) as u64;
nostr::Timestamp::from(new_timestamp)
}
}
Expand Down
1 change: 1 addition & 0 deletions crates/mdk-sqlite-storage/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

### Changed

- Upgraded `nostr` dependency from 0.43 to 0.44, replacing deprecated `Timestamp::as_u64()` calls with `Timestamp::as_secs()` ([#162](https://github.com/marmot-protocol/mdk/pull/162))
- **Persistent Snapshots**: Implemented snapshot support by copying group-specific rows to a dedicated snapshot table. `create_group_snapshot`, `rollback_group_to_snapshot`, and `release_group_snapshot` persist across app restarts. ([#152](https://github.com/marmot-protocol/mdk/pull/152))
- **Unified Storage Architecture**: `MdkSqliteStorage` now directly implements OpenMLS's `StorageProvider<1>` trait instead of wrapping `openmls_sqlite_storage`. This enables atomic transactions across MLS and MDK state, which is required for proper commit race resolution per MIP-03. ([#148](https://github.com/marmot-protocol/mdk/pull/148))
- Removed `openmls_sqlite_storage` dependency
Expand Down
2 changes: 1 addition & 1 deletion crates/mdk-sqlite-storage/src/groups.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ impl GroupStorage for MdkSqliteStorage {

let last_message_id: Option<&[u8; 32]> =
group.last_message_id.as_ref().map(|id| id.as_bytes());
let last_message_at: Option<u64> = group.last_message_at.as_ref().map(|ts| ts.as_u64());
let last_message_at: Option<u64> = group.last_message_at.as_ref().map(|ts| ts.as_secs());

self.with_connection(|conn| {
conn.execute(
Expand Down
4 changes: 2 additions & 2 deletions crates/mdk-sqlite-storage/src/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ impl MessageStorage for MdkSqliteStorage {
message.pubkey.as_bytes(),
message.kind.as_u16(),
message.mls_group_id.as_slice(),
message.created_at.as_u64(),
message.created_at.as_secs(),
&message.content,
&tags_json,
&event_json,
Expand Down Expand Up @@ -123,7 +123,7 @@ impl MessageStorage for MdkSqliteStorage {
params![
&processed_message.wrapper_event_id.to_bytes(),
&message_event_id,
&processed_message.processed_at.as_u64(),
&processed_message.processed_at.as_secs(),
&processed_message.epoch,
&mls_group_id,
&processed_message.state.to_string(),
Expand Down
2 changes: 1 addition & 1 deletion crates/mdk-sqlite-storage/src/welcomes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ impl WelcomeStorage for MdkSqliteStorage {
params![
processed_welcome.wrapper_event_id.as_bytes(),
welcome_event_id,
processed_welcome.processed_at.as_u64(),
processed_welcome.processed_at.as_secs(),
processed_welcome.state.as_str(),
&processed_welcome.failure_reason
],
Expand Down
4 changes: 4 additions & 0 deletions crates/mdk-uniffi/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
### Breaking changes

- **Security (Audit Issue M)**: Changed `get_message()` to require both `mls_group_id` and `event_id` parameters. This prevents messages from different groups from overwriting each other by scoping lookups to a specific group. ([#124](https://github.com/marmot-protocol/mdk/pull/124))

### Changed

- Upgraded `nostr` dependency from 0.43 to 0.44, replacing deprecated `Timestamp::as_u64()` calls with `Timestamp::as_secs()` ([#162](https://github.com/marmot-protocol/mdk/pull/162))
- Changed `get_messages()` to accept optional `limit` and `offset` parameters for pagination control. Existing calls must be updated to pass `None, None` for default behavior (limit: 1000, offset: 0), or specify values for custom pagination. ([#111](https://github.com/marmot-protocol/mdk/pull/111))
- Changed `get_pending_welcomes()` to accept optional `limit` and `offset` parameters for pagination control. Existing calls must be updated to pass `None, None` for default behavior (limit: 1000, offset: 0), or specify values for custom pagination. ([#119](https://github.com/marmot-protocol/mdk/pull/119))
- Changed `new_mdk()`, `new_mdk_with_key()`, and `new_mdk_unencrypted()` to accept an optional `MdkConfig` parameter for customizing MDK behavior. Existing calls must be updated to pass `None` for default behavior. ([`#155`](https://github.com/marmot-protocol/mdk/pull/155))
Expand Down
4 changes: 2 additions & 2 deletions crates/mdk-uniffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1081,7 +1081,7 @@ impl From<group_types::Group> for Group {
image_nonce: g.image_nonce.map(|n| n.as_ref().to_vec()),
admin_pubkeys: g.admin_pubkeys.iter().map(|pk| pk.to_hex()).collect(),
last_message_id: g.last_message_id.map(|id| id.to_hex()),
last_message_at: g.last_message_at.map(|ts| ts.as_u64()),
last_message_at: g.last_message_at.map(|ts| ts.as_secs()),
epoch: g.epoch,
state: g.state.as_str().to_string(),
}
Expand Down Expand Up @@ -1134,7 +1134,7 @@ impl From<message_types::Message> for Message {
event_id: m.wrapper_event_id.to_hex(),
sender_pubkey: m.pubkey.to_hex(),
event_json,
processed_at: m.created_at.as_u64(),
processed_at: m.created_at.as_secs(),
kind: m.kind.as_u16(),
state: m.state.as_str().to_string(),
}
Expand Down