diff --git a/README.md b/README.md
index 978dfa135d6e4762576e43c0ee6c623b98f9df1f..910fbe4cfcf4a39d4b3b9577c693a1b38866a8a0 100644
--- a/README.md
+++ b/README.md
@@ -7,8 +7,24 @@ as well as text on
 ## Formal specifications in this repo
 
 - [Binary Data Format](BDF.md)
+
+### Bramble Protocols
+
 - [Bramble Handshake Protocol](protocols/BHP.md)
 - [Bramble QR Code Protocol](protocols/BQP.md)
 - [Bramble Rendezvous Protocol](protocols/BRP.md)
 - [Bramble Synchronisation Protocol](protocols/BSP.md)
 - [Bramble Transport Protocol](protocols/BTP.md)
+
+### Briar BSP Clients
+
+* [Transport Key Agreement Client](clients/Transport-Key-Agreement-Client.md)
+* [Transport Properties Client](clients/Transport-Properties-Client.md)
+* [Messaging Client](clients/Messaging-Client.md)
+* [Forum Client](clients/Forum-Client.md)
+* [Forum Sharing Client](clients/Forum-Sharing-Client.md)
+* [Blog Client](clients/Blog-Client.md)
+* [Blog Sharing Client](clients/Blog-Sharing-Client.md)
+* [Private Group Client](clients/Private-Group-Client.md)
+* [Private Group Sharing Client](clients/Private-Group-Sharing-Client.md)
+* [Introduction Client](clients/Introduction-Client.md)
diff --git a/assets/clients/Blog-Sharing-Client/state-machine-4.odg b/assets/clients/Blog-Sharing-Client/state-machine-4.odg
new file mode 100644
index 0000000000000000000000000000000000000000..dcbe3b3032ca93db81b86e4e9cd12463cf76a733
Binary files /dev/null and b/assets/clients/Blog-Sharing-Client/state-machine-4.odg differ
diff --git a/assets/clients/Blog-Sharing-Client/state-machine-4.png b/assets/clients/Blog-Sharing-Client/state-machine-4.png
new file mode 100644
index 0000000000000000000000000000000000000000..edaff36cd69932465735b66b30f76c194af513d9
Binary files /dev/null and b/assets/clients/Blog-Sharing-Client/state-machine-4.png differ
diff --git a/assets/clients/Forum-Sharing-Client/state-machine-4.odg b/assets/clients/Forum-Sharing-Client/state-machine-4.odg
new file mode 100644
index 0000000000000000000000000000000000000000..dcbe3b3032ca93db81b86e4e9cd12463cf76a733
Binary files /dev/null and b/assets/clients/Forum-Sharing-Client/state-machine-4.odg differ
diff --git a/assets/clients/Forum-Sharing-Client/state-machine-4.png b/assets/clients/Forum-Sharing-Client/state-machine-4.png
new file mode 100644
index 0000000000000000000000000000000000000000..edaff36cd69932465735b66b30f76c194af513d9
Binary files /dev/null and b/assets/clients/Forum-Sharing-Client/state-machine-4.png differ
diff --git a/assets/clients/Introduction-Client/introducee-state-machine-3a.odg b/assets/clients/Introduction-Client/introducee-state-machine-3a.odg
new file mode 100644
index 0000000000000000000000000000000000000000..30f86165ab35daee92274f12b0eb0b94ce30b324
Binary files /dev/null and b/assets/clients/Introduction-Client/introducee-state-machine-3a.odg differ
diff --git a/assets/clients/Introduction-Client/introducee-state-machine-3a.png b/assets/clients/Introduction-Client/introducee-state-machine-3a.png
new file mode 100644
index 0000000000000000000000000000000000000000..5f8a0d82e76e2b24b2e3292749315245707df696
Binary files /dev/null and b/assets/clients/Introduction-Client/introducee-state-machine-3a.png differ
diff --git a/assets/clients/Introduction-Client/introducer-state-machine-3a.odg b/assets/clients/Introduction-Client/introducer-state-machine-3a.odg
new file mode 100644
index 0000000000000000000000000000000000000000..c277330686303edad204f5ad7a491c887b63b99a
Binary files /dev/null and b/assets/clients/Introduction-Client/introducer-state-machine-3a.odg differ
diff --git a/assets/clients/Introduction-Client/introducer-state-machine-3a.png b/assets/clients/Introduction-Client/introducer-state-machine-3a.png
new file mode 100644
index 0000000000000000000000000000000000000000..2e02fe9874e493c36d4987b0d1b5d49dd9dfcb99
Binary files /dev/null and b/assets/clients/Introduction-Client/introducer-state-machine-3a.png differ
diff --git a/assets/clients/Private-Group-Sharing-Client/creator-state-machine.odg b/assets/clients/Private-Group-Sharing-Client/creator-state-machine.odg
new file mode 100644
index 0000000000000000000000000000000000000000..9413b636f153d906e1aee95563c1b2d3a8f1549a
Binary files /dev/null and b/assets/clients/Private-Group-Sharing-Client/creator-state-machine.odg differ
diff --git a/assets/clients/Private-Group-Sharing-Client/creator-state-machine.png b/assets/clients/Private-Group-Sharing-Client/creator-state-machine.png
new file mode 100644
index 0000000000000000000000000000000000000000..d4cd6a22ab29156ddcc2f3016b50939ba21929ee
Binary files /dev/null and b/assets/clients/Private-Group-Sharing-Client/creator-state-machine.png differ
diff --git a/assets/clients/Private-Group-Sharing-Client/invitee-state-machine.odg b/assets/clients/Private-Group-Sharing-Client/invitee-state-machine.odg
new file mode 100644
index 0000000000000000000000000000000000000000..a67c2c6e250c19c2d364eae545124021cde181ed
Binary files /dev/null and b/assets/clients/Private-Group-Sharing-Client/invitee-state-machine.odg differ
diff --git a/assets/clients/Private-Group-Sharing-Client/invitee-state-machine.png b/assets/clients/Private-Group-Sharing-Client/invitee-state-machine.png
new file mode 100644
index 0000000000000000000000000000000000000000..3e150befdfed7cc0bfa86cf6c100662387827f81
Binary files /dev/null and b/assets/clients/Private-Group-Sharing-Client/invitee-state-machine.png differ
diff --git a/assets/clients/Private-Group-Sharing-Client/peer-state-machine.odg b/assets/clients/Private-Group-Sharing-Client/peer-state-machine.odg
new file mode 100644
index 0000000000000000000000000000000000000000..a3dab7c845986f3bfbb27fd471c40bfb84754c60
Binary files /dev/null and b/assets/clients/Private-Group-Sharing-Client/peer-state-machine.odg differ
diff --git a/assets/clients/Private-Group-Sharing-Client/peer-state-machine.png b/assets/clients/Private-Group-Sharing-Client/peer-state-machine.png
new file mode 100644
index 0000000000000000000000000000000000000000..7240567b48df5d41196afd2846e8e17ac533ebd6
Binary files /dev/null and b/assets/clients/Private-Group-Sharing-Client/peer-state-machine.png differ
diff --git a/assets/clients/Transport-Key-Agreement-Client/key-agreement-client.odg b/assets/clients/Transport-Key-Agreement-Client/key-agreement-client.odg
new file mode 100644
index 0000000000000000000000000000000000000000..e135cd5a9361fd5a70c03160b88663b4afc13878
Binary files /dev/null and b/assets/clients/Transport-Key-Agreement-Client/key-agreement-client.odg differ
diff --git a/assets/clients/Transport-Key-Agreement-Client/key-agreement-client.png b/assets/clients/Transport-Key-Agreement-Client/key-agreement-client.png
new file mode 100644
index 0000000000000000000000000000000000000000..acdb75e388a01a293df2010a122b7f4c9ac08541
Binary files /dev/null and b/assets/clients/Transport-Key-Agreement-Client/key-agreement-client.png differ
diff --git a/clients/Blog-Client.md b/clients/Blog-Client.md
new file mode 100644
index 0000000000000000000000000000000000000000..6a30d375045b10d18f6de5ed1954358aa3654261
--- /dev/null
+++ b/clients/Blog-Client.md
@@ -0,0 +1,62 @@
+# Blog Client
+
+The blog client is a [BSP client](https://code.briarproject.org/briar/briar-spec/blob/master/protocols/BSP.md) that synchronises blog posts among groups of devices. It is used in conjunction with the [blog sharing client](blog sharing client).
+
+The creator of a blog is the only user who can post messages. Posts and comments from other blogs can be reblogged with optional comments.
+
+A blog may consist of posts imported from an RSS feed.
+
+### Identifier
+
+The client's identifier is `org.briarproject.briar.blog`. The major version is 0.
+
+### Groups
+
+Each blog is represented by a separate BSP group. The [group descriptor](https://code.briarproject.org/briar/briar-spec/blob/master/protocols/BSP.md#23-groups) is a [BDF list](https://code.briarproject.org/briar/briar-spec/blob/master/BDF.md) with two elements: `author` (list) and `rss` (boolean).
+
+`author` is a list with three elements: `formatVersion` (int), `nickname` (string) and `publicKey` (raw). This identifies the user who publishes the blog.
+
+`rss` indicates whether the blog contains an imported RSS feed or a user's personal blog. `nickname` is used as the blog's title. Posts are signed with the private key corresponding to `publicKey`.
+
+### Message types
+
+**0: POST** - A blog post. The message body is a BDF list with three elements: `messageType` (int), `text` (string), and `signature` (raw).
+
+The signature covers a BDF list with three elements: `groupId` (unique ID), `timestamp` (int), and `text` (string). The group ID and timestamp are taken from the message header. The public key from the group descriptor is used for verifying the signature. The signature label is `org.briarproject.briar.blog/POST`.
+
+**1: COMMENT** - A pointer to a reblogged post or comment, with an optional comment. The message body is a BDF list with five elements: `messageType` (int), `comment` (string or null), `parentOriginalId` (unique ID), `parentId` (unique ID), and `signature` (raw).
+
+`parentOriginalId` is the ID of a post or comment in this blog or another blog. `parentId` is the ID of this comment's parent, which is a post, comment, wrapped post or wrapped comment in this blog, which had the ID `parentOriginalId` in the blog where the parent was originally posted.
+
+The signature covers a BDF list with five elements: `groupId` (unique ID), `timestamp` (int), `comment` (string or null), `parentOriginalId` (unique ID), and `parentId` (unique ID). The group ID and timestamp are taken from the message header. The public key from the group descriptor is used for verifying the signature. The signature label is `org.briarproject.briar.blog/COMMENT`.
+
+**2: WRAPPED_POST** - A reblogged post from another blog. The message body is a BDF list with five elements: `messageType` (int), `copiedGroupDescriptor` (list), `copiedTimestamp` (int), `copiedText` (string), and `copiedSignature` (raw).
+
+`copiedGroupDescriptor` is the descriptor of the blog where this post was originally posted. `copiedTimestamp`, `copiedText` and `copiedSignature` are copied from the original post. The public key from the copied group descriptor is used for verifying the signature. The signature label is `org.briarproject.briar.blog/POST`.
+
+The original group ID must be calculated, as it is covered by the signature. The original message ID must also be calculated, as it is referenced by comments.
+
+**3: WRAPPED_COMMENT** - A reblogged comment from another blog. The message body is a BDF list with eight elements: `messageType` (int), `copiedGroupDescriptor` (list), `copiedTimestamp` (int), `copiedComment` (string or null), `copiedParentOriginalId` (unique ID), `copiedParentId` (unique ID), `copiedSignature` (raw), and `parentId` (unique ID).
+
+`copiedGroupDescriptor` is the descriptor of the blog where this comment was originally posted. `copiedTimestamp`, `copiedComment`, `copiedParentOriginalId`, `copiedParentId` and `copiedSignature` are copied from the original comment. The public key from the copied group descriptor is used for verifying the signature. The signature label is `org.briarproject.briar.blog/COMMENT`.
+
+The original group ID must be calculated, as it is covered by the signature. The original message ID must also be calculated, as it is referenced by comments.
+
+`parentId` is the ID of this comment's parent, which is a post, comment, wrapped post or wrapped comment in this blog, which had the ID `copiedParentOriginalId` in the blog where the parent was originally posted, and the ID `copiedParentId` in the blog where this comment was originally posted.
+
+### Validity policy
+
+* A post is valid if it is well-formed and has a valid signature.
+* A comment is valid if it is well-formed, has a valid signature, and references a valid message of any type in the same blog, with a matching original ID.
+* A wrapped post is valid if a valid post can be reconstructed from it.
+* A wrapped comment is valid if a valid comment can be reconstructed from it, and it references a valid message of any type in the same blog, with a matching original ID.
+
+### Storage policy
+
+* All messages are stored.
+
+### Sharing policy
+
+* All posts and comments are shared.
+* Wrapped posts and wrapped comments are not shared unless they are referenced by a post or comment.
+
diff --git a/clients/Blog-Sharing-Client.md b/clients/Blog-Sharing-Client.md
new file mode 100644
index 0000000000000000000000000000000000000000..7b1bc4227fd50b8710d059948c8460eec29afc13
--- /dev/null
+++ b/clients/Blog-Sharing-Client.md
@@ -0,0 +1,74 @@
+# Blog Sharing Client
+
+The blog sharing client is a [BSP client](https://code.briarproject.org/briar/briar-spec/blob/master/protocols/BSP.md) that allows users to share blogs with their contacts, who may accept or decline the invitations. It is used in conjunction with the [blog client](blog client).
+
+### Identifier
+
+The client's identifier is `org.briarproject.briar.blog.sharing`. The major version is 0.
+
+### Groups
+
+The client uses a separate BSP group for communicating with each contact. The [group descriptor](https://code.briarproject.org/briar/briar-spec/blob/master/protocols/BSP.md#23-groups) is a [BDF list](https://code.briarproject.org/briar/briar-spec/blob/master/BDF.md) containing the unique IDs of the contacts' identities, sorted in ascending order as byte strings.
+
+### Roles
+
+All communication happens between two contacts, who have symmetrical roles.
+
+### Sessions
+
+The messages exchanged between two contacts referring to a given blog constitute a session. The blog's unique ID is used as the session ID.
+
+### Message types
+
+The protocol uses five message types.
+
+**0: INVITE** - Sent by either party to start or restart a session. The message body is a BDF list with four elements: `messageType` (int), `previousMessageId` (unique ID or null), `descriptor` (list), and `text` (string or null).
+
+`previousMessageId` is the ID of the previous message in this session, if any. `descriptor` is the [descriptor of the blog](Blog-Client#group-identifiers) being shared. The blog ID must be calculated from the descriptor, as it is used by subsequent messages in the session. `text` is an optional message from the inviter to the invitee explaining the invitation.
+
+The sender sets the blog's visibility to VISIBLE when sending an invite message.
+
+**1: ACCEPT** - Sent in response to an invite. The message body is a BDF list with three elements: `messageType` (int), `blogId` (unique ID), and `previousMessageId` (unique ID).
+
+`previousMessageId` is the ID of the previous message in this session.
+
+The sender sets the blog's visibility to SHARED when sending an accept message. The recipient sets the blog's visibility to SHARED when receiving an accept message.
+
+**2: DECLINE** - Sent in response to an invite. The message body is a BDF list with three elements: `messageType` (int), `blogId` (unique ID), and `previousMessageId` (unique ID).
+
+`previousMessageId` is the ID of the previous message in this session.
+
+The inviter sets the blog's visibility to INVISIBLE when receiving a decline message.
+
+**3: LEAVE** - Sent by either party when unsubscribing from the blog, if the blog's visibility is VISIBLE or SHARED. The message body is a BDF list with three elements: `messageType` (int), `blogId` (unique ID), and `previousMessageId` (unique ID).
+
+`previousMessageId` is the ID of the previous message in this session.
+
+The sender sets the blog's visibility to INVISIBLE when sending a leave message. The recipient sets the blog's visibility to INVISIBLE when receiving a leave message.
+
+**4: ABORT** - Sent by either party when recieving a message that is valid but unexpected in the current state. The message body is a BDF list with three elements: `messageType` (int), `blogId` (unique ID), and `previousMessageId` (unique ID).
+
+`previousMessageId` is the ID of the previous message in this session.
+
+The sender sets the blog's visibility to INVISIBLE when sending an abort message. The recipient sets the blog's visibility to INVISIBLE when receiving an abort message.
+
+### State machine
+
+![state-machine-4](/assets/clients/Blog-Sharing-Client/state-machine-4.png)
+
+[state-machine-4.odg](/assets/clients/Blog-Sharing-Client/state-machine-4.odg)
+
+Aborting from any state returns the session to the START state.
+
+### Validity policy
+
+* A message is valid if it is well-formed and its previous message (if any) is a valid message in the same session.
+
+### Storage policy
+
+* All messages are stored.
+
+### Sharing policy
+
+* All local messages are shared.
+
diff --git a/clients/Forum-Client.md b/clients/Forum-Client.md
new file mode 100644
index 0000000000000000000000000000000000000000..e195e71b65b2f64f0fdb30504766606e5483cfc4
--- /dev/null
+++ b/clients/Forum-Client.md
@@ -0,0 +1,34 @@
+# Forum Client
+
+The forum client is a [BSP client](https://code.briarproject.org/briar/briar-spec/blob/master/protocols/BSP.md) that synchronises forum posts among groups of devices. It is used in conjunction with the [forum sharing client](forum sharing client).
+
+Any user who subscribes to a forum can post messages. Posts are signed by their authors.
+
+### Identifier
+
+The client's identifier is `org.briarproject.briar.forum`. The major version is 0.
+
+### Groups
+
+Each forum is represented by a separate BSP group. The [group descriptor](https://code.briarproject.org/briar/briar-spec/blob/master/protocols/BSP.md#23-groups) is a [BDF list](https://code.briarproject.org/briar/briar-spec/blob/master/BDF.md) with two elements: `name` (string) and `salt` (raw). The salt is 32 random bytes, to prevent collisions between forums with the same name.
+
+### Message types
+
+**POST** - The message body is a BDF list with four elements: `parentId` (unique ID or null), `author` (list), `text` (string), and `signature` (raw).
+
+`parentId` is the optional ID of a post in the same forum to which this post replies. `author` is a list with three elements: `formatVersion` (int), `nickname` (string), and `publicKey` (raw). This identifies the author of the post.
+
+The signature covers a BDF list with five elements: `groupId` (unique ID), `timestamp` (int), `parentId` (unique ID or null), `author` (list), and `text` (string). The group ID and timestamp are taken from the message header. The public key from `author` is used to verify the signature. The signature label is `org.briarproject.briar.forum/POST`.
+
+### Validity policy
+
+* A post is valid if it is well-formed, has a valid signature, and its parent (if any) is valid.
+
+### Storage policy
+
+* All messages are stored.
+
+### Sharing policy
+
+* All messages are shared.
+
diff --git a/clients/Forum-Sharing-Client.md b/clients/Forum-Sharing-Client.md
new file mode 100644
index 0000000000000000000000000000000000000000..91672d9cdb05408dadb7ab08584fad1ab20a15ed
--- /dev/null
+++ b/clients/Forum-Sharing-Client.md
@@ -0,0 +1,74 @@
+# Forum Sharing Client
+
+The forum sharing client is a [BSP client](https://code.briarproject.org/briar/briar-spec/blob/master/protocols/BSP.md) that allows users to share forums with their contacts, who may accept or decline the invitations. It is used in conjunction with the [forum client](forum client).
+
+### Identifier
+
+The client's identifier is `org.briarproject.briar.forum.sharing`. The major version is 0.
+
+### Groups
+
+The client uses a separate BSP group for communicating with each contact. The [group descriptor](https://code.briarproject.org/briar/briar-spec/blob/master/protocols/BSP.md#23-groups) is a [BDF list](https://code.briarproject.org/briar/briar-spec/blob/master/BDF.md) containing the unique IDs of the contacts' identities, sorted in ascending order as byte strings.
+
+### Roles
+
+All communication happens between two contacts, who have symmetrical roles.
+
+### Sessions
+
+The messages exchanged between two contacts referring to a given forum constitute a session. The forum's unique ID is used as the session ID.
+
+### Message types
+
+The protocol uses five message types.
+
+**0: INVITE** - Sent by either party to start or restart a session. The message body is a BDF list with four elements: `messageType` (int), `previousMessageId` (unique ID or null), `descriptor` (list), and `text` (string or null).
+
+`previousMessageId` is the ID of the previous message in this session, if any. `descriptor` is the [descriptor of the forum](Forum-Client#group-identifiers) being shared. The forum ID must be calculated from the descriptor, as it is used by subsequent messages in the session. `text` is an optional message from the inviter to the invitee explaining the invitation.
+
+The sender sets the forum's visibility to VISIBLE when sending an invite message.
+
+**1: ACCEPT** - Sent in response to an invite. The message body is a BDF list with three elements: `messageType` (int), `forumId` (unique ID), and `previousMessageId` (unique ID).
+
+`previousMessageId` is the ID of the previous message in this session.
+
+The sender sets the forum's visibility to SHARED when sending an accept message. The recipient sets the forum's visibility to SHARED when receiving an accept message.
+
+**2: DECLINE** - Sent in response to an invite. The message body is a BDF list with three elements: `messageType` (int), `forumId` (unique ID), and `previousMessageId` (unique ID).
+
+`previousMessageId` is the ID of the previous message in this session.
+
+The recipient sets the forum's visibility to INVISIBLE when receiving a decline message.
+
+**3: LEAVE** - Sent by either party when unsubscribing from the forum, if the forum's visibility is VISIBLE or SHARED. The message body is a BDF list with three elements: `messageType` (int), `forumId` (unique ID), and `previousMessageId` (unique ID).
+
+`previousMessageId` is the ID of the previous message in this session.
+
+The sender sets the forum's visibility to INVISIBLE when sending a leave message. The recipient sets the forum's visibility to INVISIBLE when receiving a leave message.
+
+**4: ABORT** - Sent by either party when recieving a message that is valid but unexpected in the current state. The message body is a BDF list with three elements: `messageType` (int), `forumId` (unique ID), and `previousMessageId` (unique ID).
+
+`previousMessageId` is the ID of the previous message in this session.
+
+The sender sets the forum's visibility to INVISIBLE when sending an abort message. The recipient sets the forum's visibility to INVISIBLE when receiving an abort message.
+
+### State machine
+
+![state-machine-4](/assets/clients/Forum-Sharing-Client/state-machine-4.png)
+
+[state-machine-4.odg](/assets/clients/Forum-Sharing-Client/state-machine-4.odg)
+
+Aborting from any state returns the session to the START state.
+
+### Validity policy
+
+* A message is valid if it is well-formed and its previous message (if any) is a valid message in the same session.
+
+### Storage policy
+
+* All messages are stored.
+
+### Sharing policy
+
+* All local messages are shared.
+
diff --git a/clients/Introduction-Client.md b/clients/Introduction-Client.md
new file mode 100644
index 0000000000000000000000000000000000000000..9299b126aeda3fa79199c8b319636492cdfeb759
--- /dev/null
+++ b/clients/Introduction-Client.md
@@ -0,0 +1,98 @@
+# Introduction Client
+
+The introduction client is a [BSP client](https://code.briarproject.org/briar/briar-spec/blob/master/protocols/BSP.md) that allows a user to introduce two contacts to each other. Each contact may accept or decline the introduction. If both contacts accept, they become each other's contacts.
+
+### Identifier
+
+The client's identifier is `org.briarproject.briar.introduction`. The major version is 1.
+
+### Groups
+
+The client uses a separate BSP group for communicating with each contact. The [group descriptor](https://code.briarproject.org/briar/briar-spec/blob/master/protocols/BSP.md#23-groups) is a [BDF list](https://code.briarproject.org/briar/briar-spec/blob/master/BDF.md) containing the unique IDs of the contacts' identities, sorted in ascending order as byte strings.
+
+### Roles
+
+All communication happens between two contacts, who may take different roles with respect to each introduction. The contact who initiated the introduction takes the role of **introducer**. The other contact takes the role of **introducee**. Each introduction involves two introducees, who are referred to as **Alice** and **Bob**. Alice is the introducee whose unique ID is lower, comparing the IDs as byte strings.
+
+### Sessions
+
+The messages exchanged between a given introducer and (unordered) pair of introducees constitute a session. For the introducer, the session uses the BSP groups shared with the two introducees. For each introducee, the session uses the BSP group shared with the introducer. The introducees do not communicate directly.
+
+The session ID is the hash of a BDF list containing the unique ID of the introducer, followed by the unique IDs of the introducees sorted in ascending order as byte strings. The hash label is `org.briarproject.briar.introduction/SESSION_ID`.
+
+### Message types
+
+#### Message types:
+
+The protocol uses six message types.
+
+**0: REQUEST** - Sent by the introducer to each introducee. The message body is a BDF list with four elements: `messageType` (int), `previousMessageId` (unique ID or null), `contact` (list), and `text` (string or null).
+
+`previousMessageId` is the unique ID of the previous message sent by the sender in this session, if any, which is a dependency.
+
+`contact` is the identity of the other introducee, which is a list with three elements: `formatVersion` (int), `nickname` (string), and `publicKey` (raw).
+
+`text` is an optional message from the introducer to the introducee explaining the introduction.
+
+**1: ACCEPT** - Sent by an introducee to the introducer if the sender accepts the introduction. Forwarded by the introducer to the other introducee. The message body is a BDF list with six elements: `messageType` (int), `sessionId` (unique ID), `previousMessageId` (unique ID), `ephemeralPublicKey` (raw), `timestamp` (int), and `transportProperties` (dictionary).
+
+Each key of the `transportProperties` dictionary is a transport ID. The value is a dictionary containing properties for the respective transport, where the keys and values are strings. The `transportProperties` dictionary may not be empty.
+
+**2: DECLINE** - Sent by an introducee to the introducer if the sender declines the introduction. Forwarded by the introducer to the other introducee. The message body is a BDF list with three elements: `messageType` (int), `sessionId` (unique ID), and `previousMessageId` (unique ID).
+
+**3: AUTH** - Sent by an introducee to the introducer if accept messages have been sent and received. Forwarded by the introducer to the other introducee. The message body is a BDF list with five elements: `messageType` (int), `sessionId` (unique ID), `previousMessageId` (unique ID), `mac` (raw), and `signature` (raw).
+
+Alice calculates the master key as `SHARED_SECRET("org.briarproject.briar.introduction/MASTER_KEY", bobEphemeralPublicKey, aliceEphemeralPrivateKey, clientMajorVersion, aliceEphemeralPublicKey, bobEphemeralPublicKey)`, where `clientMajorVersion` is the byte string `0x01`.
+
+Bob calculates the master key as `SHARED_SECRET("org.briarproject.briar.introduction/MASTER_KEY", aliceEphemeralPublicKey, bobEphemeralPrivateKey, clientMajorVersion, aliceEphemeralPublicKey, bobEphemeralPublicKey)`. If the introducer forwards the ephemeral public keys without modifying them, this is the same value calculated by Alice.
+
+The MAC sent by Alice covers a BDF list with three elements: `introducerId` (unique ID), `aliceInfo` (list), and `bobInfo` (list). The MAC label is `org.briarproject.briar.introduction/AUTH_MAC`. Alice's MAC key is `KDF("org.briarproject.briar.introduction/ALICE_MAC_KEY", masterKey)`.
+
+The `aliceInfo` and `bobInfo` lists contain the information sent by Alice and Bob, respectively. Each list has four elements: `authorId` (unique ID), `timestamp` (int), `ephemeralPublicKey` (raw), and `transportProperties` (dictionary).
+
+The signature sent by Alice is `SIGN("org.briarproject.briar.introduction/AUTH_SIGN", aliceIdentityPrivateKey, aliceNonce)`, where `aliceNonce` is `MAC("org.briarproject.briar.introduction/AUTH_NONCE", aliceMacKey)`.
+
+The MAC sent by Bob covers a BDF list with three elements: `introducerId` (unique ID), `bobInfo` (list), and `aliceInfo` (list). The MAC label is `org.briarproject.briar.introduction/AUTH_MAC`. Bob's MAC key is `KDF("org.briarproject.briar.introduction/BOB_MAC_KEY", masterKey)`.
+
+The signature sent by Bob is `SIGN("org.briarproject.briar.introduction/AUTH_SIGN", bobIdentityPrivateKey, bobNonce)`, where `bobNonce` is `MAC("org.briarproject.briar.introduction/AUTH_NONCE", bobMacKey)`.
+
+Each introducee derives transport keys from the master key before sending an auth message, then deletes her ephemeral private key and the master key. The MAC keys are retained for sending and receiving activate messages.
+
+**4: ACTIVATE** - Sent by an introducee to the introducer if auth messages have been sent and received. Forwarded by the introducer to the other introducee. The message body is a BDF list with four elements: `messageType` (int), `sessionId` (unique ID), `previousMessageId` (unique ID), and `mac` (raw).
+
+The MAC sent by Alice is `MAC("org.briarproject.briar.introduction/ACTIVATE_MAC", aliceMacKey)`. The MAC sent by Bob is `MAC("org.briarproject.briar.introduction/ACTIVATE_MAC", bobMacKey)`.
+
+Each introducee deletes the MAC keys after sending and receiving activate messages.
+
+**5: ABORT** - Sent by any party to abort the protocol. The message body is a BDF list with three elements: `messageType` (int), `sessionId` (unique ID), and `previousMessageId` (unique ID).
+
+Each introducee deletes her ephemeral private key, the master key, and the MAC keys before sending an abort message, or after receiving an abort message.
+
+### State machines
+
+Introducer state machine:
+
+![introducer-state-machine-3a](/assets/clients/Introduction-Client/introducer-state-machine-3a.png)
+
+[introducer-state-machine-3a.odg](/assets/clients/Introduction-Client/introducer-state-machine-3a.odg)
+
+Introducee state machine:
+
+![introducee-state-machine-3a](/assets/clients/Introduction-Client/introducee-state-machine-3a.png)
+
+[introducee-state-machine-3a.odg](/assets/clients/Introduction-Client/introducee-state-machine-3a.odg)
+
+### Validity policy
+
+* A request, accept or decline message is valid if it is well-formed and its previous message (if any) is a valid message from the same sender in the same session.
+* An auth message is valid if it is well-formed and its previous message (if any) is a valid message from the same sender in the same session. An introducee receiving an auth message must also validate the MAC using the other introducee's MAC key and the signature using the other introducee's identity key.
+* An activate message is valid if it is well-formed and its previous message (if any) is a valid message from the same sender in the same session. An introducee receiving an activate message must also validate the MAC using the other introducee's MAC key.
+
+### Storage policy
+
+* All messages are stored.
+
+### Sharing policy
+
+* All local messages are shared.
+
diff --git a/clients/Messaging-Client.md b/clients/Messaging-Client.md
new file mode 100644
index 0000000000000000000000000000000000000000..f8cf0268ce711a36aab65f5149a13188cbb791f6
--- /dev/null
+++ b/clients/Messaging-Client.md
@@ -0,0 +1,28 @@
+# Messaging Client
+
+The messaging client is a [BSP client](https://code.briarproject.org/briar/briar-spec/blob/master/protocols/BSP.md) that synchronises private messages between pairs of devices.
+
+### Identifier
+
+The client's identifier is `org.briarproject.briar.messaging`. The major version is 0.
+
+### Groups
+
+The client uses a separate BSP group for communicating with each contact. The [group descriptor](https://code.briarproject.org/briar/briar-spec/blob/master/protocols/BSP.md#23-groups) is a [BDF list](https://code.briarproject.org/briar/briar-spec/blob/master/BDF.md) containing the unique IDs of the contacts' identities, sorted in ascending order as byte strings.
+
+### Message types
+
+**PRIVATE_MESSAGE** - The message body is a BDF list with one element: `text` (string).
+
+### Validity policy
+
+* A private message is valid if it is well-formed.
+
+### Storage policy
+
+* All messages are stored.
+
+### Sharing policy
+
+* All local messages are shared.
+
diff --git a/clients/Private-Group-Client.md b/clients/Private-Group-Client.md
new file mode 100644
index 0000000000000000000000000000000000000000..128d0a2408b48fcec8e0bf3a1543da10ee9902aa
--- /dev/null
+++ b/clients/Private-Group-Client.md
@@ -0,0 +1,49 @@
+# Private Group Client
+
+The private group client is a [BSP client](https://code.briarproject.org/briar/briar-spec/blob/master/protocols/BSP.md) that synchronises messages among groups of devices. It is used in conjunction with the [private group sharing client](private group sharing client).
+
+The creator of each private group is the only user who can invite other members. Any member can post messages to a group. Messages are signed by their authors.
+
+### Identifier
+
+The client's identifier is `org.briarproject.briar.privategroup`. The major version is 0.
+
+### Groups
+
+Each private group is represented by a separate BSP group. The [group descriptor](https://code.briarproject.org/briar/briar-spec/blob/master/protocols/BSP.md#23-groups) is a [BDF list](https://code.briarproject.org/briar/briar-spec/blob/master/BDF.md) with three elements: `creator` (list), `name` (string), and `salt` (raw).
+
+`creator` is a list with three elements: `formatVersion` (int), `nickname` (string), and `publicKey` (raw). This identifies the user who created the private group. The salt is 32 random bytes, to prevent collisions between private groups with the same creator and name.
+
+### Message types
+
+**0: JOIN** - Sent by a new member to announce that she has joined the private group. The message body is a BDF list with four elements: `messageType` (int), `member` (list), `invite` (list or null), and `memberSignature` (raw).
+
+`member` is a list with three elements: `formatVersion` (int), `nickname` (string), and `publicKey` (raw). This identifies the user who has joined the private group.
+
+`invite` is null if the member created the private group. Otherwise it is a list with two elements: `inviteTimestamp` (int) and `inviteSignature` (raw). These are copied from the [invitation](Private-Group-Sharing-Client#message-types) sent by the creator.
+
+`inviteSignature` covers a BDF list with three elements: `inviteTimestamp` (int), `contactGroupId` (unique ID), and `privateGroupId` (unique ID). `contactGroupId` is the ID of the group used by the creator and the member for [private group invitations](Private-Group-Sharing-Client#group-identifiers) - it can be calculated from the identities of the creator and the member. The public key from the private group descriptor is used to verify `inviteSignature`. The signature label is `org.briarproject.briar.privategroup.invitation/INVITE`.
+
+`memberSignature` covers a BDF list with four elements: `privateGroupId` (unique ID), `timestamp` (int), `member` (list), and `invite` (list or null). The group ID and timestamp are taken from the message header. The public key from `member` is used to verify `memberSignature`. The signature label is `org.briarproject.briar.privategroup/JOIN`.
+
+**1: POST** - The message body is a BDF list with six elements: `messageType` (int), `member` (list), `parentId` (unique ID or null), `previousMessageId` (unique ID), `text` (string), and `signature` (raw).
+
+`member` is a list with three elements: `formatVersion` (int), `nickname` (string), and `publicKey` (raw). This identifies the author of the message.
+
+`parentId` is the optional ID of a message in the same private group to which this message replies. `previousMessageId` is the ID of the previous join or post message sent by this member to this private group.
+
+The signature covers a BDF list with six elements: `groupId` (unique ID), `timestamp` (int), `member` (list), `parentId` (unique ID or null), `previousMessageId` (unique ID), and `text` (string). The group ID and timestamp are taken from the message header. The public key from `member` is used to verify the signature. The signature label is `org.briarproject.briar.privategroup/POST`.
+
+### Validity policy
+
+* A join is valid if it is well-formed, has a valid signature from the new member, and has a valid signature from the creator unless the new member is the creator.
+* A post is valid if it is well-formed, has a valid signature, its parent (if any) is a valid post, and its previous message is a valid join or post by the same member.
+
+### Storage policy
+
+* All messages are stored.
+
+### Sharing policy
+
+* All messages are shared.
+
diff --git a/clients/Private-Group-Sharing-Client.md b/clients/Private-Group-Sharing-Client.md
new file mode 100644
index 0000000000000000000000000000000000000000..fe13a43e154e327e0cea1e7afee9c9dcd4c9f9e3
--- /dev/null
+++ b/clients/Private-Group-Sharing-Client.md
@@ -0,0 +1,87 @@
+# Private Group Sharing Client
+
+The private group sharing client is a [BSP client](https://code.briarproject.org/briar/briar-spec/blob/master/protocols/BSP.md) that allows users to share private groups with their contacts, who may accept or decline the invitations. It is used in conjunction with the [private group client](private group client).
+
+### Identifier
+
+The client's identifier is `org.briarproject.briar.privategroup.invitation`. The major version is 0.
+
+### Groups
+
+The client uses a separate BSP group for communicating with each contact. The [group descriptor](https://code.briarproject.org/briar/briar-spec/blob/master/protocols/BSP.md#23-groups) is a [BDF list](https://code.briarproject.org/briar/briar-spec/blob/master/BDF.md) containing the unique IDs of the contacts' identities, sorted in ascending order as byte strings.
+
+### Roles
+
+All communication happens between two contacts, who may take different roles with respect to each private group. If one of the contacts created the private group, that contact takes the role of **creator** and the other takes the role of **invitee**. Otherwise both contacts take the role of **peer**. 
+
+### Sessions
+
+The messages exchanged between two contacts referring to a given private group constitute a session. The private group's unique ID is used as the session ID.
+
+### Message types
+
+The protocol uses four message types.
+
+**0: INVITE** - Sent by the creator to the invitee. The message body is a BDF list with six elements: `messageType` (int), `creator` (list), `groupName` (string), `salt` (raw), `text` (string or null), and `signature` (raw).
+
+`creator`, `groupName` and `salt` are taken from the private group descriptor. `creator` must be the identity of the contact who sent the message.
+
+`text` is an optional message from the creator to the invitee explaining the invitation.
+
+The signature covers a BDF list with three elements: `timestamp` (int), `contactGroupId` (unique ID), and `privateGroupId` (unique ID). The timestamp and contact group ID are taken from the message header. The private group ID is calculated from the private group descriptor. The public key from the private group descriptor is used to validate the signature. The signature label is `org.briarproject.briar.privategroup.invitation/INVITE`.
+
+The creator sets the private group's visibility to VISIBLE when sending an invite message.
+
+**1: JOIN** - Sent by an invitee in response to an invite, or by a peer when sharing the private group with a contact who is also a member. The message body is a BDF list with three elements: `messageType` (int), `privateGroupId` (unique ID), and `previousMessageId` (unique ID).
+
+`previousMessageId` is the ID of the previous message in this session.
+
+An invitee sets the private groups's visibility to SHARED when sending a join message. The creator sets the private group's visibility to SHARED when receiving a join message.
+
+A peer sets the private group's visibility to VISIBLE or SHARED when sending or receiving a join message, depending on the state machine.
+
+**2: LEAVE** - Sent by an invitee in response to an invite, or by an invitee or a peer when leaving the private group, or by the creator when dissolving the private group. The message body is a BDF list with three elements: `messageType` (int), `privateGroupId` (unique ID), and `previousMessageId` (unique ID).
+
+`previousMessageId` is the ID of the previous message in this session.
+
+The sender sets the private group's visibility to INVISIBLE when sending a leave message. The recipient sets the private group's visibility to INVISIBLE when receiving a leave message.
+
+**3: ABORT** - Sent by either party when recieving a message that is valid but unexpected in the current state. The message body is a BDF list with two elements: `messageType` (int) and `privateGroupId` (unique ID).
+
+The sender sets the private group's visibility to INVISIBLE when sending an abort message. The recipient sets the private group's visibility to INVISIBLE when receiving an abort message.
+
+### State machines
+
+Creator state machine:
+
+![creator-state-machine](/assets/clients/Private-Group-Sharing-Client/creator-state-machine.png)
+
+[creator-state-machine.odg](/assets/clients/Private-Group-Sharing-Client/creator-state-machine.odg)
+
+Invitee state machine:
+
+![invitee-state-machine](/assets/clients/Private-Group-Sharing-Client/invitee-state-machine.png)
+
+[invitee-state-machine.odg](/assets/clients/Private-Group-Sharing-Client/invitee-state-machine.odg)
+
+Peer state machine:
+
+![peer-state-machine](/assets/clients/Private-Group-Sharing-Client/peer-state-machine.png)
+
+[peer-state-machine.odg](/assets/clients/Private-Group-Sharing-Client/peer-state-machine.odg)
+
+"Member announcement" in the peer state machine indicates receipt of the contact's [join message in the private group](Private-Group-Client#message-types), demonstrating that the contact is a member.
+
+### Validity policy
+
+* An invite message is valid if it is well-formed, is sent by the group creator, and has a valid signature from the group creator.
+* A join, leave or abort message is valid if it is well-formed and its previous message (if any) is a valid message in the same session.
+
+### Storage policy
+
+* All messages are stored.
+
+### Sharing policy
+
+* All local messages are shared.
+
diff --git a/clients/Transport-Key-Agreement-Client.md b/clients/Transport-Key-Agreement-Client.md
new file mode 100644
index 0000000000000000000000000000000000000000..d0c5cf9292f43eeb9b3deeeb56ab34916b3d4da9
--- /dev/null
+++ b/clients/Transport-Key-Agreement-Client.md
@@ -0,0 +1,97 @@
+# Transport Key Agreement Client
+
+The transport key agreement client is a [BSP client](https://code.briarproject.org/briar/briar-spec/-/blob/master/protocols/BSP.md) that establishes keys for newly added transports.
+
+The client establishes transport keys with each contact for any transports that were added more recently than the contact was added.
+
+A key agreement session may be started between two peers, X and Y, in the following cases:
+
+1. Neither of the peers supported the transport when they added each other as contacts. In this case, each peer will send a key message when it adds support for the transport. If a peer receives a key message for a transport it doesn't support, it will defer handling the message until it supports the transport. The session will complete when both peers support the transport.
+2. X did not support the transport when the peers added each other as contacts, but Y did. In this case, when X later adds support for the transport, X will start a key agreement session. Although Y already has keys for the transport, it will complete the session in order to establish a new set of keys that X also possesses.
+
+### Identifier
+
+The client's identifier is `org.briarproject.bramble.transport.agreement`. The major version is 0.
+
+### Groups
+
+The client uses a separate BSP group for communicating with each contact. The [group descriptor](https://code.briarproject.org/briar/briar-spec/-/blob/master/protocols/BSP.md#23-groups) is a [BDF list](https://code.briarproject.org/briar/briar-spec/-/blob/master/BDF.md) containing the unique IDs of the contacts' identities, sorted in ascending order as byte strings.
+
+### Message types
+
+The protocol uses two message types.
+
+**0: KEY** - The message body is a BDF list with three elements: `messageType` (int), `transportId` (string), and `publicKey` (raw). `publicKey` is an X25519 public key.
+
+**1: ACTIVATE** - The message body is a BDF list with three elements: `messageType` (int), `transportId` (string), and `previousMessageId` (unique ID). `previousMessageId` is the ID of the sender's key message in this session, which is a dependency.
+
+### Validity policy
+
+* A key message is valid if it is well-formed and the public key is valid, i.e. the X25519 raw shared secret is not all zeroes.
+* An activate message is valid if it is well-formed and its dependency is a valid incoming key message with the same transport ID.
+
+### Storage policy
+
+* A key message for an unknown transport is deferred.
+* All other messages are stored.
+
+### Sharing policy
+
+* All local messages are shared.
+
+### State machine
+
+![key-agreement-client](/assets/clients/Transport-Key-Agreement-Client/key-agreement-client.png)
+
+[key-agreement-client.odg](/assets/clients/Transport-Key-Agreement-Client/key-agreement-client.odg)
+
+The `ACTIVATED` state lets us know that we already completed a session, so that we don't create a new session if we receive another key message from the contact. Otherwise a malicious contact could cause us to create an unlimited number of key sets and run out of memory.
+
+### Key management
+
+For each session, each peer generates an ephemeral X25519 key pair. The public key is sent in the peer's key message and the private key is stored until the peer receives the other peer's key message. Each peer then uses its own private key and the other peer's public key to derive the [root key](https://code.briarproject.org/briar/briar-spec/blob/master/protocols/BTP.md#2-key-management-protocol) for a set of rotation mode transport keys.
+
+Each peer stores the transport keys, starts rotating them, and deletes its own private key before sending its activate message. A peer does not start using the new transport keys until it either receives the other peer's activate message or receives a BTP stream from the other peer that uses the new keys.
+
+#### Roles
+
+The peers assign themselves the roles of Alice and Bob by comparing their ephemeral public keys as byte strings. The peer whose ephemeral public key comes first is Alice, the other is Bob.
+
+#### Notation
+
+* || denotes concatenation
+
+* Double quotes denote an ASCII string
+
+* len(x) denotes the length of x in bytes
+
+* int\_n(x) denotes x represented as an unsigned, big-endian, n-bit integer
+
+#### Cryptographic primitives
+
+The protocol uses two primitives:
+
+1. **A cryptographic hash function**, H(m). In the current version of the protocol this is BLAKE2b.
+
+2. **A key agreement function**, DH(pri, pub), where pri is one party's private key and pub is the other party's public key. In the current version of the protocol this is X25519.
+
+We use H(m) to define a multi-argument hash function:
+
+* HASH(x\_1, ..., x\_n) = H(int\_32(len(x\_1)) || x\_1 || ... || int\_32(len(x\_n)) || x\_n)
+
+#### Key derivation
+
+Alice calculates the raw shared secret as follows:
+
+* raw = DH(pri\_a, pub\_b)
+
+Bob calculates the raw shared secret as follows:
+
+* raw = DH(pri\_b, pub\_a)
+
+(*Note:* If a peer calculates an X25519 raw shared secret that is all zeroes, the peer must reject the incoming key message and not continue with the session.)
+
+Both peers then derive the root key as follows:
+
+* root\_key = HASH("org.briarproject.bramble.transport.agreement/ROOT\_KEY", raw, pub\_a, pub_b)
+
diff --git a/clients/Transport-Properties-Client.md b/clients/Transport-Properties-Client.md
new file mode 100644
index 0000000000000000000000000000000000000000..93cd0511ce49a3114dd9025b9db66fb0746a7855
--- /dev/null
+++ b/clients/Transport-Properties-Client.md
@@ -0,0 +1,34 @@
+# Transport Properties Client
+
+The transport properties client is a [BSP client](https://code.briarproject.org/briar/briar-spec/blob/master/protocols/BSP.md) that synchronises transport properties between pairs of devices. Transport properties describe how to connect to a device over various transports.
+
+### Identifier
+
+The client's identifier is `org.briarproject.bramble.properties`. The major version is 0.
+
+### Groups
+
+The client uses a separate BSP group for communicating with each contact. The [group descriptor](https://code.briarproject.org/briar/briar-spec/blob/master/protocols/BSP.md#23-groups) is a [BDF list](https://code.briarproject.org/briar/briar-spec/blob/master/BDF.md) containing the unique IDs of the contacts' identities, sorted in ascending order as byte strings.
+
+The client also uses a group with an empty descriptor for storing local transport properties. This group is not shared with any contacts.
+
+### Message types
+
+**UPDATE** - The message body is a BDF list with three elements: `transportId` (string), `version` (int), and `properties` (dictionary). `transportId` and `properties` are supplied by the transport plugin. The keys and values of `properties` are strings. `version` is incremented whenever the properties change.
+
+### Validity policy
+
+* An update is valid if it is well-formed.
+
+### Storage policy
+
+* In the groups shared with contacts:
+    * For each transport, the local message with the highest version is stored.
+    * For each transport, the remote message with the highest version is stored.
+* In the unshared group, the local message with the highest version is stored.
+
+### Sharing policy
+
+* In the groups shared with contacts, all local messages are shared.
+* In the unshared group, no messages are shared.
+