Skip to content

Cryptid's Identity System

Instead of traditional accounts (like alice@server.com with a password), Cryptid uses a two-layer identity system. Each user has a persistent User Identity that’s shared across devices, while each device maintains its own Device Identity for MLS cryptographic operations. This separation enables seamless multi-device support while maintaining strong cryptographic guarantees.

  • User Account: alice@server.com
  • Password: hunter2
  • Server stores:
    • Username
    • Password hash
    • User data

The trust model essentially boils down to “Server vouches that alice@server.com is legitimate”.

User Identity: Persistent Ed25519 keypair shared across all your devices

  • Used for application-layer operations (contact exchange, group invitations)
  • Enables multi-device coordination
  • First device creates it, subsequent devices receive it via secure provisioning

Device Identity: Per-device Ed25519 keypair (distinct from User Identity)

  • Used exclusively for MLS cryptographic operations
  • Each device has its own Device ID and keypair
  • Enables independent operation and device-level revocation

The server stores nothing permanent about either identity.

Here, the trust model becomes “This message can be verified cryptographically at multiple layers”:

  • MLS Layer: Device signatures prove message authenticity
  • Application Layer: User signatures prove identity ownership

The fundamental cryptographic identity for each device in the Cryptid protocol.

DeviceIdentity Structure
struct DeviceIdentity {
// Core cryptographic identity (permanent, MLS layer only)
device_id: [u8; 32],
// Device keypair used exclisively for MLS operations
keypair: Ed25519KeyPair,
// Delivery addresses (ephemeral, rotatable)
delivery_addresses: Vec<DeliveryAddress>
// Unix timestamp of creation
created_timestamp: u64,
}
  • device_id: [u8; 32]

    • Blake3 hash of the device’s Ed25519 public key
    • Permanent identifier for this device
    • Used only at the MLS cryptographic layer
    • MUST NOT change over the device’s lifetime
  • keypair: Ed25519KeyPair

    • Ed25519 keypair for MLS cryptographic operations
    • Private key MUST be stored securely and never transmitted
    • Public key is used to derive device_id
    • Used exclusively for MLS protocol operations
  • delivery_addresses: Vec<DeliveryAddress>

    • Collection of ephemeral addresses for message routing
    • Separate from cryptographic identity for privacy
    • Can be rotated periodically
    • Multiple addresses MAY be active simultaneously
  • created_timestamp: u64

    • Unix timestamp of device identity creation
    • Used for calculating device age for progressive trust
    • MUST NOT be modified after creation
  • Device Identity Generation:

    • Device identity MUST be generated locally without server coordination
    • device_id MUST be derived from the public key (not random)
    • keypair MUST use RFC 8032 compliant Ed25519 cryptopgraphy
    • Initial delivery_addresses MAY be empty
  • Security Requirements:

    • Private key MUST never be transmitted over the network
    • Device identity SHOULD persist across app restarts
    • device_id MUST be unique within the protocol

The fundamental cryptographic identity for each user in the Cryptid protocol.

UserIdentity Structure
struct UserIdentity {
// Blake3 hash of user public key
user_id: [u8; 32],
// User-level Ed25519 keypair (for application layer only)
keypair: Ed25519KeyPair,
// All devices controlled by this user
devices: Vec<DevicePublicInfo>,
// Unix timestamp of initial creation
created_at: u64,
// Default persona
default_persona: Persona,
// Additional personas (indexed by NonZeroU16)
personas: HashMap<NonZeroU16, Persona>,
}
  • user_id: [u8; 32]

    • Blake3 hash of the device’s Ed25519 public key
    • Permanent identifier for this user across ALL their devices
    • Used at the application layer (NOT for MLS operations)
    • MUST NOT change over the user’s lifetime
  • keypair: Ed25519KeyPair

    • Ed25519 keypair for application-layer operations
    • Used for signing user-level actions (device linking, profile updates)
    • NOT used for MLS cryptographic operations
    • Private key shared and securely provisioned across all the user’s devices
  • devices: Vec<DevicePublicInfo>

    • List of all devices linked to this user identity
    • Each device has its own cryptographic identity for MLS
    • Used for multi-device coordination and group invitations
    • MUST contain at least one device
    • See: DevicePublicInfo Structure
  • created_at: u64

    • Unix timestamp of user identity creation
    • Corresponds to when the first device was provisioned
    • MUST NOT be modified after creation
  • default_persona: Persona

    • Primary persona containing display name and profile information
    • Used when no specific persona is selected
    • MUST always be present
  • personas: HashMap<NonZerou16, Persona>

    • Optional additional personas for different contexts
    • Indexed by non-zero persona ID
    • Allows users to maintain separate identities within the protocol
    • MAY be empty
  • User Identity Generation:

    • User identity MUST be generated on the first device
    • Subsequent devices receive the user identity via secure provisioning
    • user_id MUST be derived from the public key (not random)
    • User Identity MUST be shared securely between a user’s devices
  • Multi-Device Coordination:

    • All devices under one user identity share the same user_id and keypair
    • Each device MUST have a unique DeviceIdentity for MLS operations
    • Adding a device requires a secure device linking protocol
    • User identity enables inviting all user’s devices to groups at once
  • Security Requirements:

    • User keypair MUST be provisioned securely to new devices
    • Device linking MUST use ephemeral key exchange
    • User identity MUST NOT be exposed to servers
  • User Identity (Application Layer):

    • Persistent across devices
    • Used for contact exchange, profiles, multi-device coordination
    • One per user
  • Device Idenetity (MLS Layer):

    • Unique to each device
    • Used for MLS cryptographic operations
    • One per device

A user with N devices has:

  • 1 UserIdentity (shared across all devices)
  • N DeviceIdentities (one per device)

Delivery addresses are ephemeral routing identifiers separate from the cryptographic Device Identity. This separation enables address rotation for privacy without affecting the permanent device identity.

DeliveryAddress Structure
struct DeliveryAddress {
// Address identifier (e.g., "a1b2c3d4e5...")
prefix: String,
// Server domain (e.g., "chat.example.com")
server: String,
// Timestamp of when the address was generated
created_at: u64,
// Can be deactivated without deletion
active: bool,
}
impl DeliveryAddress {
fn full_address(&self) -> String {
format!("{}@{}", self.prefix, self.server)
}
}
  • prefix: String

    • Random identifier component of the address (e.g., a1b2c3d4...)
    • Generated using cryptographically secure randomness
    • Combined with server domain to form complete address
    • SHOULD be sufficiently long to avoid collisions (recommended: 16+ characters)
  • server: String

    • Domain name of the home server hosting this delivery address (e.g., chat.example.com)
    • Used for message routing across the federated network
    • Devices MAY have addresses on multiple different servers (multi-homing)
    • MUST be a valid domain name
    • MAY include a port number if non-standard
  • created_at: u64

    • Unix timestamp of when the address was generated
    • Used for address rotation policies and cleanup
    • MUST NOT be modified after creation
    • Enables time-based address management
  • active: bool

    • Indicates whether this address can currently receive messages
    • Allows deactivation without deletion for gradual rotation
    • Inactive addresses MAY be retained temporarily for message delivery
    • MUST be set to false during address rotation before creating new address
  • Address Generation:

    • Delivery addresses MUST be generated with cryptographically secure randomness
    • Prefix MUST be unique within the device’s address set
    • Server domain identifies which server hosts this particular address
    • Devices MAY generate addresses on multiple servers for multi-homing
    • New addresses SHOULD be generated periodically for privacy
  • Address Rotation:

    • Devices MAY rotate addresses to prevent tracking or burn addresses that have become to spammy
    • Old addresses SHOULD remain active temporarily to receive in-flight messages
    • Address rotation MUST NOT affect the underlying device identity
    • Rotation period is Implementation-defined but SHOULD be at least 12 hours
  • Multi-Homing:

    • Devices MAY announce themselves on multiple servers simultaneously
    • Each server assignment results in a separate delivery address
    • Multi-homing provides redundancy if one server becomes unavailable
    • Multi-homing enables load balancing across federated servers
    • Senders MAY choose any active address that they know of when routing messages
    • All addresses for a device point to the same cryptographic identity
  • Message Routing:

    • Servers route messages based on the full address (prefix@server)
    • Messages MUST only be delivered to active addresses
    • Servers SHOULD NOT store delivery address history
    • Address lookup MUST NOT reveal device identity to servers
  • Privacy Considerations:

    • Delivery addresses are routing metadata visible to servers
    • Frequent rotation enhances privacy by preventing long-term attacks
    • Addresses SHOULD be treated as temporary identifiers only
    • Device identity remains constant regardless of address changes
  • Device Identity (Permanent)

    • Cryptographic keypair-based
    • Used for MLS operations
    • Never changes
    • Not visible to servers
  • Delivery Address (Ephemeral)

    • Random routing identifier
    • Used for message delivery
    • Rotates periodically
    • Visible to servers for routing only

A device has:

  • One permanent Device Identity
  • Multiple delivery addresses over its lifetime
  • MAY have addresses on multiple servers (multi-homing)

A subset of DeviceIdentity that is safe to share with other users as part of your UserIdentity.

DevicePublicInfo Structure
struct DevicePublicInfo {
// Blake3 hash of device's public key (permanent, for MLS layer only)
device_id: [u8; 32],
// The device's public key
public_key: Ed25519PublicKey,
// Address to initially contact this device
initial_delivery_address: DeliveryAddress,
// Server that hosts the KeyPackages for this device
keypackage_server: String,
// Timestamp of when it was linked by the user
linked_at: u64,
}
  • device_id: [u8; 32]

    • Blake3 hash of the device’s Ed25519 public key
    • Distributed as part of an InfoPackage
    • Used by other users to reference this device in multi-device contexts
    • MUST remain stable for the lifetime of this device entry in UserIdentity.devices
  • public_key: Ed25519PublicKey

    • The public half of this device’s keypair
    • Used to verify device-level signatures
    • MUST correspond to to the device’s private key used for MLS operations
  • initial_delivery_address: DeliveryAddress

    • Default address other users SHOULD use for first contact with this device
    • Specifies both the routing prefix and the server domain for message delivery
    • Used for sending initial Welcome messages and early direct messages
    • MAY be rotated later; the field captures the address active at link time
  • keypackage_server: String

    • Base domain or URL of the server hosting this device’s MLS KeyPackages
    • Used by contacts to fetch KeyPackages when adding this device to groups
    • MUST be reachable over HTTPS for interoperable deployments
  • linked_at: u64

    • Unix timestamp of when this device was linked to the UserIdentity
    • Used for UX (e.g., linked 3 days ago) and device management views
    • MAY be used for heuristics like device age or in client UIs
    • MUST be set at link time and SHOULD NOT be modified afterward
  • DevicePublicInfo Construction

    • DevicePublicInfo MUST be derived from an existing valid DeviceIdentity
    • device_id and public_key MUST match the underlying device MLS identity
    • initial_delivery_address MUST reference an active delivery address for the device at the time of linking
    • keypackage_server MUST point to a server that exposes the KeyPackage upload/fetch API for this device
  • Sharing and Privacy Requirements:

    • DevicePublicInfo MAY be shared with other users via identity InfoPackages
    • Private key material MUST NOT be included in DevicePublicInfo
    • Clients SHOULD treat DevicePublicInfo as public metadata suitable for QR codes and links
    • Removing a device from UserIdentity.devices MUST be treated as a revocation of that device for future operations

Personas allow users to maintain different identities within the Cryptid protocol. Each User Identity has a default persona and can create additional personas for different contexts (e.g., work, personal, community-specific, etc)

Persona Structure
struct Persona {
// Display name for this persona
display_name: String,
// Profile picture for this persona
profile_picture: Option<ProfilePicture>,
// Bio for this persona
bio: Option<String>,
// Pronouns for this persona
pronouns: Option<String>,
}
pub enum PersonaId {
Default,
Id(NonZeroU16),
}
  • display_name: String

    • Human readable name shown to other users
    • MUST be non-empty
    • MAY contain Unicode characters (emoji, international characters, etc)
    • Length SHOULD be limited by implementations (recommended 1-32 characters)
    • Used in contact lists, group member lists, and message displays
  • profile_picture: Option<ProfilePicture>

    • Optional profile picture for this persona
    • If None, implementations MAY display a default avatar
    • Profile pictures are signed for authenticity
    • See ProfilePicture below for structure details
  • bio: Option<String>

    • Optional biography or status message
    • If None, no bio is displayed
    • MAY contain unicode characters
    • Length SHOULD be limited by implementations (recommended 0-500 characters)
    • Used for personal descriptions, status messages, or context about the persona
  • pronouns: Option<String>

    • Optional pronouns for this persona
    • If None, no pronouns are displayed
    • SHOULD NOT contain unicode characters
    • Length SHOULD be limited by implementations (recommended 0-20 characters)
    • Used to display a persona’s pronouns
  • Persona Management

    • Every User Identity MUST have at least one persona (the default one)
    • Default persona MUST be present at user identity creation
    • Additional personas MAY be created with unique NonZeroU16 identifiers
    • Persona IDs MUST be unique within a user identity
    • Personas are stored in the user identity, not transmitted separately
  • Display Name Requirements

    • Display names MUST NOT be used as unique identifiers
    • Multiple users MAY have identical display names
    • Display names MAY be changed at any time
    • Name changes propagate through contact updates
  • Privacy Considerations

    • Personas are visible to anyone who has the user’s identity
    • Profile pictures are visible to contacts
    • Different personas do not provide cryptographic unlinkability
    • For true unlinkability, users should create separate user identity instances
  • Plural systems: Different headmates with distinct profiles
  • Role-based switching: Moderator/Admin modes with enhanced visibility

Protocol Responsibility Regarding Personas

Section titled “Protocol Responsibility Regarding Personas”

The protocol only carries persona data for rendering. Clients are responsible for how they use them. Some example client-side features might include:

  • Proxy tag detection (e.g., PluralKit-style [text] patterns)
  • Persona switching UI/UX
  • Role indicators and badges

Profile pictures provide visual identity while maintaining authenticity through signatures.

ProfilePicture Structure
struct ProfilePicture {
// e.g., "image/png"
mime_type: String,
// Raw image bytes or hash/link if large
data: Vec<u8>,
// Timestamp
uploaded_at: u64,
// Signed for authenticity
signature: Ed25519Signature,
}
  • mime_type: String

    • MIME type of the image (e.g., "image/png")
    • Implementations SHOULD support common image formats (PNG, JPEG)
    • Implementations MAY reject unsupported formats
    • MUST accurately reflect the image data format
  • data: Vec<u8>

    • Raw image bytes
    • For large images, implementations MAY use a content hash or external reference instead
    • Recommended maximum size: 256 KB (implementations MAY enforce smaller limits)
    • Image dimensions SHOULD be reasonable (e.g., 512x512 or smaller)
  • uploaded_at: u64

    • Unix timestamp of when the picture was set
    • Used for detecting profile picture updates
    • MUST NOT be backdated
  • signature: Ed25519Signature

    • Signature over the image data using the user’s keypair
    • Proves the profile picture was set by the user identity owner
    • Prevents impersonation through unauthorized profile picture changes
    • MUST be verified before accepting profile picture updates
  • Profile Picture Authentication

    • Profile pictures MUST be signed by the user’s keypair
    • Implementations MUST verify signatures before displaying pictures
    • Unsigned or incorrectly signed pictures MUST be rejected
    • Signature covers the image data and metadata
  • Size and Format

    • Implementations SHOULD enforce reasonable size limits (e.g., 256 KB)
    • Large images SHOULD be resized before signing
    • Implementations MAY transcode images to supported formats
    • Animated images (GIFs, APNGs) MAY be supported but not required
  • Updates and Propagation

    • Profile picture changes propagate through contact updates
    • Old profile pictures MAY be retained temporarily for UI consistency
    • Implementations SHOULD cache profile pictures locally
    • Cache invalidation based on uploaded_at timestamp

User IDs and Device IDs are deterministically derived from their respective public keys using Blake3 hashing.

Formula:

  • user_id = Blake3(user_public_key)
  • device_id = Blake3(device_public_key)

We do this for the following reasons:

  • Cryptographically bound: The ids cannot be forged independently of the keypair
  • Verifiable: Anyone can verify that a specific id matches the public key
  • Prevents squatting: Attackers cannot claim arbitrary user/device IDs
  • Single source of truth: The public key uniquely determines the id.

Properties:

  • Both produce 32-byte (64 hex character) identifiers
  • User ID is the same across all of a user’s devices
  • Device ID is unique to each device, even for the same user
  • No central authority needed for ID assignment

For contact exchange (QR codes, invite links), use InfoPackages instead of directly sharing identity bundles. InfoPackages provide the same functionality with added privacy and security benefits.

An InfoPackage wraps your identity information in an encrypted, server-stored package that expires automatically. When someone scans your QR code or clicks your invite link, they receive:

  • Your user_id (permanent user identity)
  • Your user_public_key (for verifying user-level signatures)
  • A list of ALL your devices with their individual device IDs, public keys, and initial delivery addresses

The QR code contains only a compact reference, not the full identity data:

CompactInfoQR Structure
struct CompactInfoQR {
// Server URL to fetch encrypted data from
info_package_url: String, // e.g., "https://chat.example.com/api/v1/infopackage/abc123xyz"
// Decryption key (client holds this, never sent to server)
info_package_key: [u8; 32],
// Type of package (for display before fetching)
package_type: InfoPackageType,
// Display name (for UI, e.g., "Alice" or "Team Chat")
display_name: String,
}
#[serde(tag = "type")]
enum InfoPackageType {
#[serde(rename = "identity")]
Identity,
#[serde(rename = "group_invite")]
GroupInvite { group_id: GroupId },
}

When decrypted, an InfoPackage contains the encrypted identity data:

IdentityInfoPackage Structure
struct IdentityInfoPackage {
user_id: UserId,
user_public_key: Ed25519PublicKey,
// Default persona (always present)
default_persona: Persona,
// Additional personas
personas: HashMap<NonZeroU16, Persona>,
// User's current devices
devices: Vec<DevicePublicInfo>,
// Profile picture reference (for fetching via device-to-device)
profile_picture: Option<ProfilePicture>,
// Metadata
created_at: u64,
}

When you upload an InfoPackage and someone scans your QR code, the InfoPackage system ensures:

  1. Encryption: Server never sees your identity data (encrypted with info_package_key)
  2. Expiration: Package automatically expires after configured TTL
  3. Revocation: You can manually revoke access at any time
  4. One-time capable: Can limit to single-use or multiple scans
  5. Compact: QR code contains only a tiny reference, not full identity

Recipients scanning your code:

  • Receive your user_id (permanent user identity)
  • Receive your user_public_key (for verifying user-level signatures)
  • See a list of ALL your devices
  • Can add all your devices to a group in a single MLS operation
  • Can inspect individual device fingerprints if desired

See Info Packages for a more detailed specification.

Devices generate KeyPackages (one-time use cryptographic material) for MLS group additions. KeyPackages are uploaded to a designated server and fetched when adding the device to groups.

When inviting a user to a group, the inviting device fetches KeyPackages for all of the user’s devices and adds them simultaneously in a single MLS commit.

For complete details on KeyPackage management, contact exchange, and trust establishment, see Contact Exchange and Trust.

User Identity vs. Device Identity: When Each Is Used

Section titled “User Identity vs. Device Identity: When Each Is Used”

Understanding which identity is used for what operation:

OperationUses User IdentityUses Device Identity
Contact exchange (QR codes)Embedded in bundle
Group invitationsAll devices invited
MLS encryption/decryption
MLS group membership
Message signing (MLS)
Display name/avatar
Multi-device linking✅ Same keypair on all devices✅ Unique per device
Device revocation✅ Signs revocationDevice being revoked

Application Layer: Uses UserIdentity for user-facing operations

  • “Alice wants to add Bob to the group”
  • Fetch Bob’s ShareableIdentityBundle (contains user_id + all their devices)
  • Fetch KeyPackages for all of Bob’s devices

MLS Layer: Uses DeviceIdentity for cryptographic operations

  • “Alice’s Phone adds Bob’s Phone, Bob’s Laptop, and Bob’s Tablet to the MLS group”
  • Three separate MLS add operations in a single commit
  • Each device independently encrypts/decrypts using its own DeviceIdentity keypair
AspectTraditional Authentication SystemsCryptid’s Device-Centric System
Account CreationServer registration requiredLocal key generation only
Identity ProofServer password verificationCryptographic signature
Trust Anchor”Server says alice@server is legitimate""Alice’s signature proves ownership”
Multi-DeviceShared account across devicesUser Identity shared across devices; Device Identities independent
Server CompromiseAll user accounts affectedIndividual devices unaffected
PrivacyUsername/email requiredNo PII needed