The Direct Message Extension introduces a secure way for two users to publish encrypted messages to each other within the twtxt ecosystem. Direct messages (DMs) leverage public/private key cryptography to ensure that only the intended recipient can read the message. This extension also enables metadata to specify a user’s public key, ensuring compatibility with encryption protocol (Curve25519).

Overview of Direct Message Format

Direct messages are encoded and include the recipient’s username and the encrypted message payload. This allows the message to remain private while adhering to the structure and principles of the twtxt format.

A typical direct message will contain the following structure:

!<nick url> <encrypted_message>

Here:

Requirements

This extension requires the following steps to send secure direct messaging:

  1. Create Public and Private Key:

    openssl genpkey -algorithm X25519 -out private_key.pem
    openssl pkey -pubout -in private_key.pem -out public_key.pem
    
  2. Public Key Declaration: A new metadata field named public_key must be present in the user’s twtxt feed. This field should contain the user’s Public Key in Base64 format.

    Example:

    base64 -w 0 < public_key.pem > public_key.b64
    

    The output, for example, will be MCowBQYDK2VuAyEAvBvdsHgzmIiRL9Mjb4fVrbSQGn4Q/m9p7XZCUDj5liI=.

    Add the public key to the twtxt feed metadata:

    # nick        = example
    # url         = https://example.com/twtxt.txt
    # avatar      = https://example.com/avatar.png
    # description = An example feed
    # public_key  = MCowBQYDK2VuAyEAvBvdsHgzmIiRL9Mjb4fVrbSQGn4Q/m9p7XZCUDj5liI=
    
  3. Encryption of DMs: Direct messages must be encrypted using the recipient’s public key to ensure confidentiality. Any modern cryptographic library supporting EC (libsocium, nacl, Go’s box/secretbox, etc.) public/private key encryption can be used for this purpose.

     wcurl https://recipient.com/twtxt.txt | grep public_key | cut -d' ' -f4 > recipient_public_key.pem
     openssl pkeyutl -derive -inkey sender_private_key.pem -peerkey recipient_public_key.pem -out shared_key.bin
     echo -n "My directo message" | openssl enc -aes-256-cbc -pbkdf2 -iter 100000 -out message.enc -pass file:shared_key.bin
     base64 -w 0 < message.enc > message.enc.b64
    

    The output, for example, will be 1234567890abcdef=.

  4. DM Syntax: Direct messages must explicitly target the recipient using the !<nick url> syntax before the encrypted payload.

    Example:

    2025-01-18T18:20:00Z !<joe https://example.com/twtxt.txt> 1234567890abcdef=
    
  5. Notify recipient (Optional): You can make a request using the Multi-User User-Agent Extension.

Format Details

Public Key Metadata

The mandatory public_key metadata field allows feed authors to publish their public key. Clients can use this key to encrypt messages for the feed author. The key must be a string. This ensures that only the author can decrypt the message using their private key.

# public_key  = MCowBQYDK2VuAyEAvBvdsHgzmIiRL9Mjb4fVrbSQGn4Q/m9p7XZCUDj5liI=

Encryption Process

When sending a direct message:

  1. Retrieve the recipient’s public key from their public_key metadata.
  2. Encrypt the plaintext message using the public key.
  3. Encode the ciphertext (Base64).
  4. Add the new line to the twtxt feed with the recipient’s nick and url followed by the encrypted message.

Example Workflow

Let’s assume two users, alice and bob, want to communicate privately. Alice wants to send a direct (she likes to encrypt the message) and Bob wants to read it (decrypt the message).

  1. Key Generation: Alice and Bob each generate a public/private key pair using Curve25519 algorithm with openssl:

    openssl genpkey -algorithm X25519 -out alice_private_key.pem
    openssl pkey -pubout -in alice_private_key.pem -out alice_public_key.pem
    
    openssl genpkey -algorithm X25519 -out bob_private_key.pem
    openssl pkey -pubout -in bob_private_key.pem -out bob_public_key.pem
    
  2. Setup:

    Alice declares her public key in her twtxt.txt metadata:

    Open alice_public_key.pem and copy the key.

    -----BEGIN PUBLIC KEY-----
     MCowBQYDK2VuAyEAvBvdsHgzmIiRL9Mjb4fVrbSQGn4Q/m9p7XZCUDj5liI=
     -----END PUBLIC KEY-----
    

    The public key is the string between -----BEGIN PUBLIC KEY----- and -----END PUBLIC KEY-----: MCowBQYDK2VuAyEAvBvdsHgzmIiRL9Mjb4fVrbSQGn4Q/m9p7XZCUDj5liI=.

    And paste it in the public_key field.

    # nick        = alice
    # url         = https://alice.example.com/twtxt.txt
    # public_key  = MCowBQYDK2VuAyEAvBvdsHgzmIiRL9Mjb4fVrbSQGn4Q/m9p7XZCUDj5liI=
    

    Bob does the same in his feed:

    # nick        = bob
    # url         = https://bob.example.com/twtxt.txt
    # public_key  = MCowBQYDK2VuAyEALBmCrrkDzfUbo3CBBvHB8R9CCD3U88xzQcohBZRGMzE=
    
  3. Encrypting the Message:

    Bob wants to send a private message to Alice. Using Alice’s public_key, Bob encrypts his message using Openssl:

    1. First, Bob retrieves Alice’s public key:
    curl https://alice.example.com/twtxt.txt | grep public_key | cut -d' ' -f4 > alice_public_key.pem
    
    1. Calculate the share key:

    Bob use his private key and Alice’s public key to calculate the shared key:

    openssl pkeyutl -derive -inkey bob_private_key.pem -peerkey alice_public_key.pem -out shared_key.bin
    

    Then, Bob encrypts the message using the shared key. The message is storage in message.enc:

     echo -n "Hi Alice, let's meet tomorrow at 5 PM!" | openssl enc -aes-256-cbc -pbkdf2 -iter 100000 -out message.enc -pass file:shared_key.bin
    
    1. Bob encodes the encrypted message in Base64:
     base64 -w 0 < message.enc > message.enc.b64
    

    Check message.enc.b64 for the encrypted message: U2FsdGVkX1+mVLsw62BUyjcjnAVtU/EP04gS9GuTsD8xW66BH3V+kb828lMswrDntCtKgauLDZEDRCmpAo3lcQ==

    This will be the string of the direct message.

  4. Sending the Message:

    Bob appends the encrypted message to his feed:

    2025-01-18T18:20:00Z !<alice https://alice.example.com/twtxt.txt> U2FsdGVkX1+mVLsw62BUyjcjnAVtU/EP04gS9GuTsD8xW66BH3V+kb828lMswrDntCtKgauLDZEDRCmpAo3lcQ==
    
  5. Reading the Message: Alice’s client fetches the message from Bob’s feed and review if the message is for her. She would see !<alice https://alice.example.com/twtxt.txt>.

    Using her private key, Alice decrypts the message:

    1. First, Alice retrieves Bob’s public key:
     curl https://bob.example.com/twtxt.txt | grep public_key | cut -d' ' -f4 > bob_public_key.pem
    
    1. Calculate the share key:

    Alice use her private key and Bob’s public key to calculate the shared key:

    openssl pkeyutl -derive -inkey alice_private_key.pem -peerkey bob_public_key.pem -out shared_key.bin
    
    1. Alice decodes the encrypted message from Base64:
    echo 'U2FsdGVkX1+mVLsw62BUyjcjnAVtU/EP04gS9GuTsD8xW66BH3V+kb828lMswrDntCtKgauLDZEDRCmpAo3lcQ==' | base64 -d > message_from_bob.enc
    
    1. Alice decrypts the message using the shared key.
    openssl enc -d -aes-256-cbc -pbkdf2 -iter 100000 -in message_from_bob.enc -out message_from_bob.txt -pass file:shared_key.bin
    

    Check message_from_bob.txt for the decrypted message.

    cat message_from_bob.txt
    

    Resulting in the plaintext message: Hi Alice, let's meet tomorrow at 5 PM!

Security Considerations

  1. Key Verification: Users should ensure public keys are genuine and match the feed owner to mitigate man-in-the-middle attacks.

  2. Encrypted Payload Visibility: While the payload is encrypted, all users viewing the feed will see the recipient nick and url. This preserves openness but prevents eavesdroppers from reading the message.

  3. Private Key Security: Users must securely store their private keys and avoid exposing them. A compromised private key can lead to the decryption of all previously sent messages.

  4. Replay Attacks: Clients should implement measures to detect and ignore duplicate messages to prevent replay attacks.

Supported Twtxt Clients

For clients to support the Direct Message Extension, they must:

  1. Parse metadata to retrieve the public_key field.
  2. Implement encryption and decryption using public/private key cryptography.
  3. Render direct messages prefixed with !<nick url>.
  4. If a client does not support this extension, it should ignore direct messages.

Changelog