Direct messages

A direct message is a messge encrypted to a feed_id When you send a direct message you can have up to 16 DM recipients.

When sending a DM message, it’s expected that you include your own feed_id as a recpient, this is so that anyone replying to your message can confidently copies the recps from your message, to the recps field of thier message and be confident you will receive it!

You can also combine DM with group_id or pobox_id, in which case whether you need to include your own feed_id depends on whether you’re part of the group or pobox

One example of these combined recps is when adding a new member to a group with a group/add-member msg. Here you have recps = [group_id, feed_id, feed_id, ...] (NOTE group_id must be in first slot, and can be followed by up to 15 other feed_id - the people you’re adding). Such a message can simultaneously: - give the new person the info needed to access the group (decrypt with a DM shared key) - reveal to the group that this action happened (decrypt with the group key)

Mapping feed_id to recp_key

When we see a feed_id in content.recps we need to map this to a key we will used in a recp_key. How we do that depends on whether the feed_id is ours (the authors).

The two cases: - A. Foreign case - mapping some elses feed_id to recp_key - B. Self case - mapping our own feed_id to recp_key

A. Foreign case - mapping some elses feed_id to recp_key

We define a shared key that the sender (us) + recipient can both derive:

const hash = 'SHA256'
const length = 32
const salt = SHA256('envelope-dm-v1-extract-salt')

function directMessageSlotKey (my_dh_secret, my_dh_public, my_id_bfe, your_dh_public, your_id_bfe) {
  var input_keying_material = scalarmult(my_dh_secret, your_dh_public)

  var info_context = Buffer.from('envelope-ssb-dm-v1/key', 'utf8')
  var info_keys = sort([
    bfe.encode(3, 0, my_dh_public) || my_id_bfe,
    bfe.encode(3, 0, your_dh_public) || your_id_bfe
  var info = slp.encode([info_context, ...info_keys])

  return hkdf(input_keying_material, length, { salt, info, hash })

The complete key-and-scheme you try with a slot is of form js { key: Buffer, // the directMessageSlotKey scheme: "envelope-id-based-dm-converted-ed25519" }

B. Self case - mapping our own feed_id to recp_key

A problem with sending a direct message to another feed is that once the message is enveloped, we don’t have a record of who the remote recipients were, so guessing which shared-key to derive (1) is hard.

A solution is to include our own feed_id as a recipient, and to always try a key we would have used on such messages. We could in theory apply the same scheme as in (1), but this would involve us doing a scalar multiplaction of the public and private parts of our key, which should be safe, but we’ve decided not to do it.

Instead, when you see your own feed_id as a recipient, you’re expected to map that to a private symmetric key, own_key. Major advantages of this approach are: - replies to original messages can use the same recps - makes coding more consistent - this is compatible with multi-device identities - later I can send a copy of keys to other devices so they have access to the same DMs

Use a key of 32 bytes

Notes: - the full key you try with a slot is of form { key, scheme: "envelope-symmetric-key-for-self" } - we considered using a “personal group”, but this has the disadvantages: - makes replies hard - remote feeds do not know how to map your personal group_id to a symmetric key, so…? - includes un-cloaked tangle info about the group, which may leak private info (e.g. feeds / devices you don’t want known)

The complete key-and-scheme for the envelope key_recp slot is of form js { key: Buffer, // the directMessageSlotKey scheme: "envelope-id-based-dm-converted-ed25519" }



Decrypting a feed_id based DM

If the encrypted message is one I sent, I can decrypt it by: - trying my own_key on all slots of the envelope - you could technically try each DM-key derived from you_dh_sk + friend_dh_pk … - DON’T do this, it’s a super expensive brute force approach - with the principle that “content.recps is conserved across messages” we don’t need to do this! - BUT, this is why feed_id based DMs is never truly one-way, even if you “omit yourself” from recps

If the encrypted message is from someone else, I can try decrypting it by: - trying a key derived from my_dh_secret and author_dh_public etc.

Using feed_id

It’s safe to use feed_id anywhere in public (as these are already public).

In the context of content.recps we use feed_id so that other recipients can see who was included in the message, but for the envelope recp_key we map the feed_id to a shared_dm_key using the above definition

var content = {
  recps: [
    '@YjoQc7sLF/ye+QM09iPcDJdzQo3lQD6YvQIFhmNbEqg=.ed25519'  // << a feed_id