Skip to content

Commit ada4b6b

Browse files
authored
Add key package hash_ref computation and deletion by serialized bytes (marmot-protocol#176)
* Add compute_key_package_hash_ref and delete_key_package_from_storage_by_hash_ref methods * Update CHANGELOG with PR marmot-protocol#176 reference for key package hash_ref methods
1 parent 5ef0c60 commit ada4b6b

2 files changed

Lines changed: 73 additions & 0 deletions

File tree

crates/mdk-core/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141

4242
### Added
4343

44+
- **KeyPackage hash_ref computation and deletion by bytes**: Added `compute_key_package_hash_ref()` to serialize a key package's hash_ref for external caching, and `delete_key_package_from_storage_by_hash_ref()` to delete a key package using previously serialized hash_ref bytes. This enables delayed key material cleanup workflows where the hash_ref is cached at welcome-processing time and used for deletion later. ([#176](https://github.com/marmot-protocol/mdk/pull/176))
4445
- **Custom Message Sort Order**: `get_messages()` now supports custom sort orders via the `Pagination::sort_order` field. Added `get_last_message(group_id, sort_order)` method to retrieve the most recent message under a given sort order, enabling clients using `ProcessedAtFirst` ordering to get a consistent "last message" value. ([#171](https://github.com/marmot-protocol/mdk/pull/171))
4546
- **`create_key_package_for_event_with_options`**: New function that allows specifying whether to include the NIP-70 protected tag. Use this if you need to publish to relays that accept protected events. ([#173](https://github.com/marmot-protocol/mdk/pull/173), related: [#168](https://github.com/marmot-protocol/mdk/issues/168))
4647
- **MIP-04 Epoch Fallback for Media Decryption**: `decrypt_from_download` now resolves the correct decryption key via an O(1) epoch hint lookup instead of only using the current epoch's exporter secret. Added `NoExporterSecretForEpoch` variant to `EncryptedMediaError` for programmatic error matching. ([#167](https://github.com/marmot-protocol/mdk/pull/167))

crates/mdk-core/src/key_packages.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
//! Nostr MLS Key Packages
22
33
use mdk_storage_traits::MdkStorageProvider;
4+
use mdk_storage_traits::mls_codec::JsonCodec;
45
use nostr::{Event, Kind, PublicKey, RelayUrl, Tag, TagKind};
6+
use openmls::ciphersuite::hash_ref::HashReference;
57
use openmls::key_packages::KeyPackage;
68
use openmls::prelude::*;
79
use openmls_basic_credential::SignatureKeyPair;
@@ -508,6 +510,36 @@ where
508510
Ok(())
509511
}
510512

513+
/// Computes and serializes the hash_ref for a key package.
514+
///
515+
/// Returns the hash_ref as serialized bytes that can be stored externally
516+
/// and later used with [`delete_key_package_from_storage_by_hash_ref`](Self::delete_key_package_from_storage_by_hash_ref)
517+
/// to delete the key package from storage.
518+
pub fn compute_key_package_hash_ref(&self, key_package: &KeyPackage) -> Result<Vec<u8>, Error> {
519+
let hash_ref = key_package.hash_ref(self.provider.crypto())?;
520+
JsonCodec::serialize(&hash_ref)
521+
.map_err(|e| Error::Provider(format!("Failed to serialize hash_ref: {}", e)))
522+
}
523+
524+
/// Deletes a key package from storage using previously serialized hash_ref bytes.
525+
///
526+
/// The `hash_ref_bytes` should be bytes previously returned by
527+
/// [`compute_key_package_hash_ref`](Self::compute_key_package_hash_ref).
528+
pub fn delete_key_package_from_storage_by_hash_ref(
529+
&self,
530+
hash_ref_bytes: &[u8],
531+
) -> Result<(), Error> {
532+
let hash_ref: HashReference = JsonCodec::deserialize(hash_ref_bytes)
533+
.map_err(|e| Error::Provider(format!("Failed to deserialize hash_ref: {}", e)))?;
534+
535+
self.provider
536+
.storage()
537+
.delete_key_package(&hash_ref)
538+
.map_err(|e| Error::Provider(e.to_string()))?;
539+
540+
Ok(())
541+
}
542+
511543
/// Generates a credential with a key for MLS (Messaging Layer Security) operations.
512544
///
513545
/// This function creates a new credential and associated signature key pair for use in MLS.
@@ -943,6 +975,46 @@ mod tests {
943975
.expect("Failed to delete key package");
944976
}
945977

978+
#[test]
979+
fn test_key_package_deletion_by_hash_ref_roundtrip() {
980+
let mdk = create_test_mdk();
981+
let test_pubkey =
982+
PublicKey::from_hex("884704bd421671e01c13f854d2ce23ce2a5bfe9562f4f297ad2bc921ba30c3a6")
983+
.unwrap();
984+
985+
let relays = vec![RelayUrl::parse("wss://relay.example.com").unwrap()];
986+
987+
// Create and parse key package
988+
let (key_package_str, _) = mdk
989+
.create_key_package_for_event(&test_pubkey, relays.clone())
990+
.expect("Failed to create key package");
991+
992+
let deletion_mdk = create_test_mdk();
993+
let key_package = deletion_mdk
994+
.parse_serialized_key_package(&key_package_str, ContentEncoding::Base64)
995+
.expect("Failed to parse key package");
996+
997+
// Compute hash_ref bytes (simulates what whitenoise-rs caches in DB)
998+
let hash_ref_bytes = deletion_mdk
999+
.compute_key_package_hash_ref(&key_package)
1000+
.expect("Failed to compute hash_ref");
1001+
1002+
assert!(
1003+
!hash_ref_bytes.is_empty(),
1004+
"hash_ref bytes should not be empty"
1005+
);
1006+
1007+
// Delete using the serialized hash_ref bytes (simulates delayed cleanup)
1008+
deletion_mdk
1009+
.delete_key_package_from_storage_by_hash_ref(&hash_ref_bytes)
1010+
.expect("Failed to delete key package by hash_ref");
1011+
1012+
// Deleting again should also succeed (idempotent, no-op)
1013+
deletion_mdk
1014+
.delete_key_package_from_storage_by_hash_ref(&hash_ref_bytes)
1015+
.expect("Second deletion should succeed (idempotent)");
1016+
}
1017+
9461018
#[test]
9471019
fn test_invalid_key_package_parsing() {
9481020
let mdk = create_test_mdk();

0 commit comments

Comments
 (0)