draft optional
This document defines how to securely share media files (images, videos, audio, documents) within Marmot groups while maintaining end-to-end encryption and privacy guarantees.
This is different to the way in which group images are encrypted and shared among group members via the marmot_group_data extension detailed in MIP-01.
Encrypted media enables secure sharing of files within Marmot groups by:
- Encrypting files using keys derived from MLS exporter secrets
- Storing encrypted blobs on content-addressed storage systems (like Blossom)
- Sharing metadata through
imetatags in Group Messages
This specification uses a versioning system to enable future evolution of the encrypted media format while maintaining backward compatibility. The version is communicated through the v field in imeta tags, which clients use to determine the appropriate decryption method.
Current Version: The specification is currently at version mip04-v2, which uses ChaCha20-Poly1305 AEAD encryption with keys derived from MLS exporter secrets and random nonces stored in the imeta tag. All new imeta tags MUST include v mip04-v2 to indicate this encryption version.
Deprecated Version: Version mip04-v1 is deprecated and MUST NOT be used for new encrypted media. Version 1 used deterministic nonce derivation via HKDF, which created security vulnerabilities due to potential nonce reuse (see Security Considerations). Clients SHOULD reject mip04-v1 tags and display appropriate warnings. Legacy media encrypted with mip04-v1 may no longer be decryptable.
Future Versions: Higher version numbers will indicate newer encryption schemes, potentially supporting features like chunked encryption, streaming decryption, or alternative cipher suites. Clients MUST verify version compatibility before attempting decryption and SHOULD gracefully handle unknown versions by displaying appropriate error messages.
Domain Separation: All versions include a scheme label (e.g., "mip04-v1" or "mip04-v2") in all cryptographic contexts (key derivation and AAD construction) to provide domain separation and prevent cross-version collisions. This ensures that keys and encrypted content from different versions cannot be confused or misused, enabling safe future upgrades.
Version 2 (Current): Uses deterministic key derivation with random nonce generation:
exporter_secret = MLS-Exporter("nostr", "nostr", 32)
file_key = HKDF-Expand(exporter_secret, "mip04-v2" || 0x00 || file_hash_bytes || 0x00 || mime_type_bytes || 0x00 || filename_bytes || 0x00 || "key", 32)
nonce = Random(12) // Cryptographically secure random 12 byte nonce
The encryption key is derived deterministically from the MLS exporter secret, while the nonce is generated randomly for each encryption operation. The random nonce MUST be stored in the imeta tag's n field (hex-encoded) and provided during decryption.
Version 1 (Deprecated): Used deterministic nonce derivation:
nonce = HKDF-Expand(exporter_secret, "mip04-v1" || 0x00 || file_hash_bytes || 0x00 || mime_type_bytes || 0x00 || filename_bytes || 0x00 || "nonce", 12)
Where the context components are constructed with exact byte layout for cross-implementation compatibility:
scheme_label = version-specific scheme label as UTF-8 bytes for domain separation
- Version 2: "mip04-v2"
- Version 1 (deprecated): "mip04-v1"
file_hash_bytes = SHA256(original_file_content) as 32 raw bytes
mime_type_bytes = MIME type in canonical form as UTF-8 (e.g., "image/jpeg")
filename_bytes = original filename as UTF-8 bytes (e.g., "document.pdf")
0x00 = single null byte separator between components
The scheme label is version-specific and MUST match the version number (e.g., "mip04-v2" for version 2, "mip04-v1" for version 1). This provides domain separation between different encryption versions and prevents cross-version collisions.
MIME Type Canonicalization: MIME types MUST be canonicalized before use in cryptographic operations:
- Convert to lowercase (e.g., "IMAGE/JPEG" → "image/jpeg")
- Trim leading and trailing whitespace
- Use standard MIME type format without parameters (e.g., "image/jpeg; charset=utf-8" → "image/jpeg")
- Apply any registered MIME type aliases to their canonical forms
This ensures consistent key derivation across implementations regardless of input formatting.
Random Nonce Generation: Version 2 uses cryptographically secure random nonce generation. Each encryption operation MUST generate a unique 12-byte nonce using a cryptographically secure random number generator. The nonce MUST be stored in the imeta tag and cannot be derived during decryption, ensuring true nonce uniqueness and preventing nonce reuse vulnerabilities.
Version 2 uses ChaCha20-Poly1305 AEAD encryption with associated data for tamper protection:
aad = "mip04-v2" || 0x00 || file_hash_bytes || 0x00 || mime_type_bytes || 0x00 || filename_bytes
nonce = Random(12) // Generated randomly, stored in imeta tag
encrypted_content = ChaCha20-Poly1305.encrypt(file_key, nonce, plaintext, aad)
The associated data (AAD) binds the scheme version, file hash, MIME type, and filename to prevent metadata tampering and provide domain separation. The nonce is randomly generated and must be provided during decryption via the imeta tag's n field.
Encrypted media files can be stored on any content-addressed storage system where files are identified by their hash. The general process is:
- Encrypt the original file using the process above
- Upload the encrypted blob to the storage system
- Address the blob using
SHA256(encrypted_content)as the identifier - Store the storage URL and metadata in
imetatags
The storage identifier (hash of encrypted content) can be used as:
- A filename in traditional file systems
- A content address in distributed storage systems
- A blob identifier in cloud storage services
- Any other hash-based addressing scheme
This specification focuses on the Blossom protocol as the primary storage backend, which provides:
- Content-addressed storage using SHA256 hashes
- Nostr-based authentication for uploads and deletions
- HTTP-based API for simple integration
- Deduplication through hash-based addressing
Content-addressed storage with encryption provides strong privacy guarantees:
- Storage addresses are hashes of encrypted content (essentially random)
- No correlation possible between original files and storage addresses
- Group privacy is the primary boundary - only group members can decrypt content
- Forward secrecy follows standard MLS behavior - new members cannot decrypt historical media
Media metadata is shared in chat messages through NIP-92 compliant imeta tags within Group Messages:
imeta url <blossom_url> m <mime_type> filename <filename> [dim <dimensions>] [blurhash <hash>] x <file_sha256> n <nonce_hex> v <version>
| Field | Description | Required |
|---|---|---|
url |
Blossom storage URL for the encrypted blob | Yes |
m |
MIME type in canonical form as UTF-8 (e.g., "image/jpeg") | Yes |
filename |
Original filename for display | Yes |
dim |
Image/video dimensions as "widthxheight" | Optional |
blurhash |
BlurHash for progressive loading | Optional |
x |
SHA256 hash of original file content (hex-encoded) | Yes |
n |
Encryption nonce (hex-encoded, 24 hex characters for 12 bytes) | Yes (v2+) |
v |
Encryption version number (currently "mip04-v2", deprecated: "mip04-v1") | Yes |
Version-Specific Fields:
- Version 2 (
mip04-v2): Thenfield is required and contains a randomly generated 12-byte nonce, hex-encoded as 24 characters. - Version 1 (
mip04-v1): Thenfield is not present (nonce was derived deterministically).⚠️ Deprecated - MUST NOT be used for new media.
The x field contains the SHA256 hash of the original file content and serves as an integrity check. After downloading and decrypting a file, clients MUST verify:
SHA256(decrypted_content) == x_field_value
This ensures the decryption was successful and the file was not corrupted.
Sending Media (Version 2):
- Sanitize file (remove EXIF, GPS, etc.)
- Hash original content for integrity verification
- Derive encryption key from current MLS exporter secret
- Generate random nonce (12 bytes, cryptographically secure)
- Encrypt file with AEAD including metadata binding
- Upload encrypted blob to Blossom storage
- Create
imetatags with metadata, nonce (nfield), and storage URL - Send Group Message containing
imetatags
Receiving Media (Version 2):
- Parse
imetatags from Group Message - Verify version is
mip04-v2(reject deprecatedmip04-v1) - Extract nonce from
nfield (hex-decode to 12 bytes) - Download encrypted blob from storage URL
- Derive encryption key using file hash and metadata from
imeta - Decrypt blob with AEAD verification using extracted nonce
- Verify integrity by checking SHA256 matches
xfield - Display and/or save decrypted content
MUST implement:
- Exact byte layout for context construction (cross-implementation compatibility)
- AEAD associated data binding to prevent metadata tampering
- Integrity verification using
xfield after decryption - Proper HKDF key derivation from MLS exporter secrets
- Random nonce generation for version 2 (cryptographically secure)
- Version-based encryption scheme for future upgrades
- Rejection of deprecated
mip04-v1version tags
MUST verify:
- Version is
mip04-v2(rejectmip04-v1with appropriate warnings) - Nonce field (
n) is present and valid (24 hex characters for version 2) - AAD matches
imetafields before accepting decrypted content - File hash integrity after decryption
- MIME type consistency between
imetaand actual content - Version compatibility before attempting decryption
This specification supports single-blob objects only. Each media file is encrypted and stored as a single atomic unit. Size and timeout limits remain a library/application-level concern.
The following features are explicitly out of scope for Version 2:
- Chunked encryption for large files
- Streaming decryption for progressive loading
- Multi-part uploads with per-chunk keys
- Cross-group deduplication (security boundary is the MLS group)
Future versions may address these features, but the base specification focuses on the simpler single-blob case.
This specification protects against:
- Storage provider compromise - Encrypted blobs reveal no information about content
- Network surveillance - All content and metadata are encrypted
- Metadata tampering - AEAD associated data prevents modification
- File corruption - Integrity verification detects corruption
- Confidentiality: Only current group members can decrypt media
- Integrity: Tampering with encrypted blobs or metadata is detectable
- Authenticity: Media origin is verifiable through MLS group membership
- Forward Secrecy: Historical media becomes inaccessible after MLS epoch changes
- Post-Compromise Security: Future media remains secure after key recovery
- Nonce Uniqueness (Version 2): Random nonce generation ensures each encryption operation uses a unique nonce, preventing nonce reuse attacks that could compromise ChaCha20-Poly1305 security
mip04-v1) used deterministic nonce derivation, which created a security vulnerability (nonce reuse risk). When the same file content was encrypted multiple times with the same filename, the deterministic nonce derivation would produce identical nonces, violating ChaCha20-Poly1305's security requirements. Version 2 fixes this by using cryptographically secure random nonces that are stored in the imeta tag, ensuring true nonce uniqueness for each encryption operation.
Migration: Clients MUST reject mip04-v1 tags and SHOULD display warnings when encountering deprecated version 1 media. New encrypted media MUST use version 2 (mip04-v2) with random nonces.