Connected to ‣

Interacting with Convos’ identity and invitation system

What happens when you start a new Convo?

A new identity is created and saved to the Keychain. An identity in Convos looks like this:

protocol ConvosIdentityType {
    var inboxId: String { get }
    var clientId: String { get }
    var clientKeys: any XMTPClientKeys { get }
}

protocol XMTPClientKeys {
    var signingKey: any XMTPiOS.SigningKey { get }
    var databaseKey: Data { get }
}

The clientId is a UUID generated on the device.

The clientKeys object contains the database key and private key for initializing an XMTP client. On iOS, we use the PrivateKey extension available in the XMTP-iOS SDK as the signingKey.

Once an XMTP client is initialized using clientKeys, the identity is saved to the Keychain with attributes that make it available only on that device.

Next, we create a new Group on XMTP to associate with this identity. When we create this group, we also generate a custom metadata object that we store in the Group.appData field. This ConversationCustomMetadata object is where we store user profiles (display name, image url), the expiration date for the conversation (when it explodes) and an inviteTag (explained below).

message ConversationCustomMetadata {
    // associates this conversation with an invite
    string tag = 1;
    // array of member profiles
    repeated ConversationProfile profiles = 2;
    // the date the conversation explodes
    optional sfixed64 expiresAtUnix = 3;
}

// each member in the group has an optional ConversationProfile
message ConversationProfile {
    bytes inboxId = 1;
    optional string name = 2;
    optional string image = 3;
}

The tag is a random string that should only be assigned once by the Group creator.

After generating the custom metadata and saving it in the description field, we create a new invite for this conversation. An invite looks like this:

message InvitePayload {
    // the XMTP group id encrypted with the `ConvosIdentity` private key
    // that generated this invite
    string conversationToken = 1;

    // The creator's inbox ID
    string creator_inbox_id = 2;

    // The tag to associate which conversation this corresponds to,
    // lives in `ConversationCustomMetadata` for the group
    string tag = 3;

    // optional conversation metadata
    // this is used to show a preview of the invite on web
    optional string name = 4;
    optional string description = 5;
    optional string imageURL = 6;
    optional sfixed64 conversationExpiresAtUnix = 7;

    // optional invite expiration
    optional sfixed64 expiresAtUnix = 8;

    // whether the invite should expire after being used once
    bool expiresAfterUse = 9;
}

// SignedInvite represents an invite with its cryptographic signature
message SignedInvite {
    // the invite payload containing the actual invite data
    InvitePayload payload = 1;

    // the signature from signing the payload with the `ConvosIdentity`
    // private key that generated this invite
    bytes signature = 2;
}

In the SignedInvite, the signature field contains the secp256k1 recoverable ECDSA signature of the invite payload**.** We then generate an invite “code” by binary serializing the SignedInvite and base64 URL encoding it. The resulting URL looks like:

<https://convos.org/v2?i=CrUCClRBUjdBbmtkREJrYVNmZ1RDTVUwd1RwNS04YmRhU3QybWlNa0hzTEwxMXhZWVZ5TE1DZnBXemFaZ3R3a2RJU0lxb3BSMGJQQWNTMzl2eV85T3ByZXASQDE2ODkyNDVmYjZkZmFmM2I1ZjljNDgzZjk0ZDI5OGE2YTRjMTI1MzU2OWE4MWYyOWZlMjkxMDYwN2NhNjQ1NDUaClNDYmJwZ09BUXYiBkNoaWNoaSoAMoQBaHR0cHM6Ly9jb252b3MtYXNzZXRzLWNvbnZvcy1vdHItZGV2LTIwMjUwNzIxMDk1MTUzNDc4MDAwMDAwMDAxLnMzLnVzLWVhc3QtMi5hbWF6b25hd3MuY29tLzY2ZWIxNTE1LWQ2Y2ItNDM3Ni05ZTJjLTQxMTJiZjIxNWI5ZS5qcGVnEkHkvTFjnxHHFCgvtNO6LbFOF36x-ZPgL0zMVeYoOf77TBIHyQ-ZLhLbhuxhc9Q79wuD1GVSx1jWf3qi36IuyQS9AQ>