Skip to content

Latest commit

 

History

History
286 lines (194 loc) · 16.1 KB

File metadata and controls

286 lines (194 loc) · 16.1 KB

MIP-00

Credentials & Key Packages

review mandatory

This document defines the credential management and KeyPackage system for Marmot Protocol. This is one of the core specifications that MUST be implemented by all projects wanting to be interoperable. Marmot identity builds on Nostr keypairs, with MLS Credentials proving group membership and KeyPackages enabling asynchronous invitations. This creates a secure bridge between Nostr's decentralized identity and MLS's group encryption.

MLS Credentials

MLS Credentials link Nostr identities to group-specific signing keys, functioning as cryptographically secure membership proofs.

Identity Requirements

When creating credentials, clients MUST:

  • Use BasicCredential type: The standard MLS credential format
  • Set identity to Nostr pubkey: The identity field MUST contain the raw 32-byte public key (not hex-encoded). This is the binary representation of the user's Nostr public key.
  • Keep identity immutable: Never allow changes to the identity field
  • Validate proposals: Reject any Proposal or Commit that attempts to change identity fields

Note on encoding: The identity field contains raw bytes. For a Nostr public key like 884704bd421671e01c13f854d2ce23ce2a5bfe9562f4f297ad2bc921ba30c3a6 (64 hex characters), the identity field should contain the 32 decoded bytes [0x88, 0x47, 0x04, 0xbd, ...], not the 64 UTF-8 bytes of the hex string.

Signing Keys

Each credential includes a unique signing key (distinct from Nostr identity) that signs MLS messages and should rotate regularly for enhanced security. The curve used for the signing key is determined by the MLS ciphersuite used by the client. All MLS ciphersuites are supported and are signaled to other users via the mls_ciphersuite tag in KeyPackage events.

KeyPackage Events

KeyPackages function as public "invitation cards" enabling asynchronous group invitations. They advertise capabilities, provide credentials, and include signing keys for authentication. Users can publish them via Nostr relays or share directly between devices.

KeyPackage Consumption and Reuse

KeyPackages are typically consumed when joining groups. To handle race conditions where multiple invites use the same KeyPackage, use the last_resort extension for reusability. Ideally, even last_resort KeyPackages are used only one time and KeyPackages should be rotated on relays and their private key material disposed of properly as soon as possible after they've been used to join a group. See KeyPackage Lifecycle Management below for more details.

Example KeyPackage Event

{
  "id": "abc123...",
  "kind": 443,
  "created_at": 1693876543,
  "pubkey": "f1e2d3c4b5a697887766554433221100ffeeddccbbaa99887766554433221100",
  "content": "SGVsbG8xMjM0NTY3ODlhYmNkZWY...",
  "tags": [
    ["mls_protocol_version", "1.0"],
    ["mls_ciphersuite", "0x0001"],
    ["mls_extensions", "0xf2ee", "0x000a"],
    ["encoding", "base64"],
    ["i", "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2"],
    ["client", "MyMLSClient"],
    ["relays", "wss://relay1.com", "wss://relay2.com"]
  ],
  "sig": "304502210..."
}

The mls_extensions tag includes marmot_group_data (0xf2ee) and last_resort (0x000a) extensions which MUST be included.

Field Explanations

Required fields:

  • content: TLS-serialized KeyPackageBundle from your MLS implementation, base64-encoded.
  • encoding: Specifies the encoding format of the content field. MUST be ["encoding", "base64"]. Implementations MUST reject events with missing or unrecognized encoding tags. Hex encoding was used by early implementations but is no longer supported and MUST be rejected.
  • mls_protocol_version: MLS protocol version - currently 1.0
  • mls_ciphersuite: MLS ciphersuite ID (see MLS spec)
  • mls_extensions: Array of supported non-default MLS extension IDs (see MLS spec). Default extensions MUST NOT be listed. This signals individual client capabilities via LeafNode.capabilities in KeyPackages. Marmot implementations MUST include the 0xf2ee extension for marmot_group_data and the 0x000a extension for last_resort.
  • relays: Relay URLs where this KeyPackage is published. MUST contain at least one valid WebSocket URL (ws:// or wss://). Used for KeyPackage discovery and deletion.
  • i: Hex-encoded KeyPackageRef for efficient relay queries (see KeyPackageRef Tag below)

Optional fields:

  • client: Client name to help with UX when users can't access signing keys (may be omitted for privacy). Format: ["client", "<name>"]
  • -: Ensures only the author can publish this event (see NIP-70). Note: Implementations SHOULD omit it unless publishing to relays known to support NIP-70 + NIP-42.

KeyPackageRef Tag

The i tag contains the hex-encoded KeyPackageRef, enabling efficient relay queries for specific KeyPackages.

Background

According to RFC 9420 Section 5.2, KeyPackageRef is computed as:

MakeKeyPackageRef(value) = RefHash("MLS 1.0 KeyPackage Reference", value)

RefHash(label, value) = Hash(RefHashInput)

struct {
  opaque label<V>;
  opaque value<V>;
} RefHashInput;

Where value is the TLS-serialized KeyPackage and Hash is the hash function from the ciphersuite specified in the KeyPackage.

Hashing Algorithm

CRITICAL: The hash function used to compute KeyPackageRef MUST be the hash function defined by the KeyPackage's ciphersuite, as specified in RFC 9420 Section 17.1. All clients MUST use the ciphersuite's hash function to ensure interoperability.

✅ Ensures interoperability by requiring all clients to use the same ciphersuite-specified hash function (e.g., SHA-256 for the default Marmot ciphersuite 0x0001) — see the ciphersuite-to-hash mapping table below.

⚠️ Using any other hash algorithm produces incompatible i tag values, causing relay queries by KeyPackageRef to silently fail across implementations.

The mapping from ciphersuite to hash function is:

Ciphersuite Value Hash Function Output Size
MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519 0x0001 SHA-256 32 bytes
MLS_128_DHKEMP256_AES128GCM_SHA256_P256 0x0002 SHA-256 32 bytes
MLS_128_DHKEMX25519_CHACHA20POLY1305_SHA256_Ed25519 0x0003 SHA-256 32 bytes
MLS_256_DHKEMX448_AES256GCM_SHA512_Ed448 0x0004 SHA-512 64 bytes
MLS_256_DHKEMP521_AES256GCM_SHA512_P521 0x0005 SHA-512 64 bytes
MLS_256_DHKEMX448_CHACHA20POLY1305_SHA512_Ed448 0x0006 SHA-512 64 bytes
MLS_256_DHKEMP384_AES256GCM_SHA384_P384 0x0007 SHA-384 48 bytes

For the default Marmot ciphersuite (0x0001), the hash function is SHA-256, producing a 32-byte (64 hex character) KeyPackageRef.

Why This Tag Is Required

Without the i tag, finding a KeyPackage by its KeyPackageRef requires:

  1. Downloading all KeyPackage events from relays
  2. Decoding each event's content
  3. Computing the KeyPackageRef for each
  4. Comparing against the target

This is computationally expensive, especially when processing Welcome messages that reference KeyPackages via KeyPackageRef (see MIP-02).

Note: Most MLS libraries (e.g., OpenMLS) provide a make_key_package_ref() function that handles this computation internally using the correct ciphersuite hash. Implementations SHOULD use their MLS library's built-in function rather than computing the hash manually.

Querying by KeyPackageRef

Clients can efficiently query relays:

const filter = {
  kinds: [443],
  "#i": ["a1b2c3d4e5f6..."]  // hex-encoded KeyPackageRef
};

Validation

When receiving KeyPackage events with an i tag, implementations SHOULD validate that the tag value matches the computed KeyPackageRef from the decoded content. Events with mismatched values SHOULD be treated as invalid.

Backward Compatibility

  • New events: MUST include the i tag
  • Existing events: May not have this tag; implementations MUST continue to support computing KeyPackageRef from event content when the tag is absent
  • Migration: Implementations MAY re-publish existing KeyPackages with the i tag added

KeyPackage Lifecycle Management

When to Delete KeyPackages (relay-based distribution)

Clients SHOULD delete KeyPackages from relays when:

  • Successfully joining a group: Delete the KeyPackage after processing a Welcome message
  • Creating new KeyPackages: Optionally replace with fresh KeyPackages

When NOT to Delete KeyPackages (relay-based distribution)

Clients MUST NOT delete KeyPackages when:

  • Welcome processing fails: If you can't process a Welcome message (e.g., signing key generated on another device)
  • Show clear errors: Display user-friendly error messages explaining the issue

Note: For direct device-to-device KeyPackage sharing (QR code, Wi-Fi, Bluetooth, NFC), deletion considerations don't apply as the KeyPackage is transferred directly rather than stored on relays.

Private Key Material Management (init_key)

Each KeyPackage contains an init_key (HPKE public key) that enables asynchronous group joining. The corresponding private key must be carefully managed to maintain forward secrecy.

init_key Lifecycle

  1. Generation: When creating a KeyPackage, the MLS library generates a fresh HPKE keypair. The public key becomes the init_key in the KeyPackage; the private key is stored locally.

  2. Usage: When processing a Welcome message (see MIP-02), the MLS library uses the private init_key to decrypt the group secrets encrypted to that KeyPackage. If the KeyPackage is marked as last_resort, then the private key is left intact, otherwise it's consumed (as stated before, this means that any Welcome messages that arrive using the same KeyPackage won't be decryptable).

  3. Deletion: After successfully joining a group via a Welcome message, the private init_key SHOULD be securely deleted from local storage.

Deletion Timing

Clients MUST carefully manage init_key deletion timing to handle race conditions:

  • Single-use KeyPackages: The private init_key MUST be deleted immediately after successfully processing the Welcome message. This is normally handled by the MLS implementation.

  • Last resort KeyPackages: The private init_key MUST be retained as long as the last resort KeyPackage remains published or you have reason to believe it will be needed to decrypt Welcome messages. The init_key MUST be deleted only when the last_resort KeyPackage is rotated and the old one is unpublished.

  • Race condition handling: If multiple Welcome messages could potentially arrive for the same KeyPackage (e.g., during the window between KeyPackage discovery and Welcome delivery), implementations SHOULD either:

    • Track which KeyPackages have pending invitations and delay deletion for a short period, OR
    • Accept that subsequent Welcomes for an already-consumed KeyPackage will fail, requiring the inviter to use a different KeyPackage

Secure Deletion Requirements

When deleting private init_key material, implementations MUST:

  • Zeroize memory: Overwrite the private key bytes with zeros before deallocation
  • Remove from persistent storage: Delete from any key storage, database, or file system
  • Clear from backups: If the implementation maintains key backups, ensure init_keys are excluded or the backup is updated

⚠️ Security Note: Retaining init_key material after joining a group creates a forward secrecy vulnerability. If an attacker later compromises the device, they could decrypt any captured Welcome messages that used that init_key, potentially gaining access to historical group state. Implementations MUST implement init_key deletion as described above.

Signing Key Rotation

All group members should regularly rotate their signing key within each group they are a part of on a regular basis. This limits compromise impact and improves forward secrecy per MLS best practices. This is done by creating an update proposal to update your own leaf node. For more info on Proposal and Commit operations please refer to MIP-03.

KeyPackage Relays List Event

When using relay-based KeyPackage distribution, users publish a kind: 10051 event to advertise which relays contain their KeyPackages. This helps others know where to look for your KeyPackages when they want to invite you to groups.

Requirements (for relay-based distribution)

  • Include relay tags: List all relays where you publish KeyPackages
  • Make them accessible: These relays SHOULD be readable by anyone you want to receive invites from
  • Keep updated: Update this list when you change your relay setup

Example Relays List Event

{
  "kind": 10051,
  "tags": [
    ["relay", "wss://inbox.nostr.wine"],
    ["relay", "wss://myrelay.nostr1.com"]
  ],
  "content": "",
  "created_at": 1693876543,
  "pubkey": "f1e2d3c4b5a697887766554433221100ffeeddccbbaa99887766554433221100",
  "sig": "304502210..."
}

Summary

This MIP defines the foundation of Marmot's identity system:

Key Components

  1. MLS Credentials: Link Nostr identities to MLS signing keys
  2. KeyPackages (kind: 443): Advertise your ability to join groups (via relays or direct sharing)
  3. Relay Lists (kind: 10051): Where to find your KeyPackages (for relay-based distribution)
  4. Signing Key Rotation: Regular key updates for enhanced security

Implementation Requirements

MUST implement:

  • Credential management linking Nostr identities to MLS signing keys
  • KeyPackage lifecycle management with last resort support
  • Regular signing key rotation in all groups
  • Immutable credential identity validation

Extension Signaling Locations

MLS extensions can be signaled in two distinct locations with different purposes:

1. Individual Client Capabilities (KeyPackage Level)

  • Location: LeafNode.capabilities in KeyPackage objects
  • Purpose: Signals what protocol features an individual client supports
  • Implementation: Advertised via the mls_extensions tag in KeyPackage events
  • Scope: Per-client capabilities that may vary between group members

2. Group-Level Requirements (GroupContext Level)

  • Location: GroupContext.extensions field
  • Purpose: Defines protocol extensions that all group members must support
  • Implementation: Updated via GroupContextExtensions proposals
  • Scope: Group-wide requirements that apply to all members

Relationship Between Capabilities and Requirements

  • Compatibility Enforcement: When processing Add proposals, implementations MUST verify that new members' LeafNode.capabilities support all GroupContext.extensions
  • Mandatory Support: Any extension in use by the group MUST be supported by all members
  • Negotiation: The RFC describes a TLS-like extension negotiation mechanism where individual capabilities are advertised and group requirements are established

Default Extensions Clarification

According to RFC 9420 Section 7.2, the following extension types are considered "default" and MUST NOT be listed in capabilities:

  • 0x0001 - application_id
  • 0x0002 - ratchet_tree
  • 0x0003 - required_capabilities
  • 0x0004 - external_pub
  • 0x0005 - external_senders

Implementations MUST:

  • Filter out default extensions when building capabilities
  • Only include custom/non-default extensions (like 0xf2ee for marmot_group_data, and 0x000a for last_resort, which Marmot implementations MUST include)
  • Consider default extensions as implicitly supported by all MLS implementations