diff --git a/blip-0031.md b/blip-0031.md
new file mode 100644
index 0000000..218effb
--- /dev/null
+++ b/blip-0031.md
@@ -0,0 +1,145 @@
+```
+bLIP: 31
+Title: Mutual Message Exchange
+Status: Active
+Author: Matt Corallo <blmmesg@bluematt.me>
+Created: 2024-01-14
+License: CC0
+```
+
+## Abstract
+
+This bLIP defines a simple protocol by which a paying party can include an encrypted message for a
+payment recipient iff both parties have indicated they wish to communicate with each other.
+
+## Copyright
+
+This bLIP is licensed under the CC0 license.
+
+## Specification
+
+This protocol is defined in terms of two parties: (a) the initiator, and (b) the message-sender. In
+the lightning context, the message-sender is the payment sender, and the initiator is the payment
+recipient.
+
+Each party is configured with a private key (`k`) and a list of acceptable public keys they wish to
+exchange messages with (`P`).
+
+Each party stores the set `s` of the result of `H(ECDH_SALT || H(ECDH(k, P_i)))` for each `P_i` in `P`.
+
+The initiator picks a random 32-byte nonce `initiator_n`. It then generates the init bytes, which are:
+ * [`u16`:`handshake_count`]
+ * [`48*handshake_count*byte`:`per_peer_handshake`]
+ * [`u16`:`repeated_data_len`]
+ * [`repeated_data_len*byte`:`repeated_data`]
+
+The initiator:
+ * MUST set `handshake_count` to a number greater than its trusted peer set count, using round
+   values to avoid revealing how many trusted peers it has configured.
+ * MUST fill in each `per_peer_handshake` with either:
+   * pseudorandom data for any excess unused trusted peer entries,
+   * `AEAD(initiator_n, s_i, PROTO_SALT, PROTO_AAD)` for each `s_i` in the initiator's `s` set.
+ * MAY include `repeated_data` which will be received with the message to avoid storing state.
+ * MUST set `repeated_data_len` to the length of `repeated_data`, if any.
+
+The message-sender:
+ * MUST ignore any bytes which are included after the end of `repeated_data`.
+ * SHOULD walk each `per_peer_handshake` and check whether it is decryptable using each `s_i` it
+   has stored.
+ * If a `per_peer_handshake` is decryptable, should send a response including the message.
+
+The message sender similarly picks a random 32-byte nonce `messenger_n` and responds with:
+ * [`u16`:`handshake_index`]
+ * [`48*byte`:`encrypted_nonce`]
+ * [`u16`:`repeated_data_len`]
+ * [`repeated_data_len*byte`:`repeated_data`]
+ * [`u16`:`message_length`]
+ * [`message_length*byte`:`encrypted_message`]
+
+The message-sender:
+ * MUST set `handshake_index` to the index of the `per_peer_handshake` they were able to decrypt
+   within the init bytes.
+ * MUST set `encrypted_nonce` to
+   `AEAD(messenger_n, H(s_i || initiator_n), PROTO_SALT ^ MASK_A, PROTO_AAD)`
+ * MUST include the same `repeated_data_len` and `repeated_data` as was sent by the initiator.
+ * MUST set `message_length` to the length of the message they wish to send, *including* the
+   16-byte MAC tag. If they only wish to communicate a message separately, `message_length` MUST be
+   set to 0.
+ * MAY calculate two message encryption keys by reading 64 bytes from
+   `ChaCha20(initiator_n ^ messenger^n, KEY_STRETCH_SALT)`. The first 32 bytes are `inline_msg_key`,
+   the next 32 bytes are `oob_msg_key`.
+ * If `message_length` is not zero, MUST set `encrypted_message` to
+   `AEAD(message, inline_msg_key, PROTO_SALT ^ MASK_B, PROTO_AAD)`
+ * MAY exchange further messages with the initiator, using the `oob_msg_key` to encrypt and
+   authenticate such messages.
+
+The initiator (message recipient):
+ * If `message_length` is between 1 and 15 (inclusive), the handshake should be treated as having
+   failed.
+ * MUST ignore any bytes which are included after the end of `encrypted_message`.
+ * SHOULD decrypt the `encrypted_nonce` and use the contained `messenger_n` to decrypt the
+   `message`.
+ * MAY exchange further messages with the message-sender, using the `oob_msg_key` to encrypt and
+   authenticate such messages.
+
+### Constants
+ * `H()` is SHA-256
+ * `ECDH()` is ECDH using secp256k1 public/secret keys.
+ * `AEAD(data, key, salt, aad)` is the ChaCha20Poly1305 RFC variant authenticated encryption
+ * `ECDH_SALT` is "Mutual Message Exchange ECDH Result"
+ * `KEY_STRETCH_SALT` is "INLINE KEY STRCH"
+ * `MASK_A` is all 0xff bytes
+ * `MASK_B` is all 0xf0 bytes
+ * `PROTO_SALT` is a salt picked to describe a specific protocol using this message exchange
+ * `PROTO_AAD` is excess data used to describe a specific instantiation of this protocol, for
+   example the specific TLV which is being used to communicate a particular type of message.
+
+## Discussion
+
+There are various times during which lightning senders may wish to include a message for the
+recipient. In many cases, the sender wishes to include such a message no matter the recipient,
+often accomplished by simply including that message as an additional TLV entry in the recipient's
+onion. However, in some cases the sender wishes to only include their message if there is mutual
+trust between the sender and the recipient.
+
+Prior to BOLT12, this was still a rather simple problem - senders could validate the recipient
+node_id against a list of parties they wished to message and then include their message in the
+onion as they otherwise would. However, with BOLT12 node_ids are no longer immediately visible.
+Worse, as the number of payment protocols continues to proliferate, senders need to have a list
+of acceptable messaging peers in multiple formats, maintained separately.
+
+This bLIP defines a simple protocol by which a paying party can include an encrypted message for a
+payment recipient iff both parties have indicated they wish to communicate with each other. It only
+requires the ability for the payment recipient to include additional bytes (~48 bytes per peer they
+are willing to exchange messages with). From there, the sender only has to include an extra 100-200
+bytes in addition to the message itself in the onion they ultimately use to send the payment.
+
+If the sender is not in the recipient's trusted peers list, they will not learn anything about who
+the recipient is. However if the recipient does trust the sender, the sender may be able to learn
+who the recipient is even if the sender does not trust the recipient (and thus no message will be
+exchanged).
+
+This protocol is envisioned for use in BOLT12 payment exchanges, however it can be used in any
+payment negotiation protocol, or even in non-payment contexts.
+
+## Rationale
+
+There are several alternative protocols which could be considered.
+
+First, the protocol could be split into one additional message - instead of the initiator being the
+payment recipient, the payment sender could initiate. This would result in less data being included
+in the final HTLC, however it would result in most of the CPU cost being borne by the (potential)
+payment recipient, rather than the payment sender. This could allow for nodes to initiate the
+protocol repeatedly with a victim, DoSing them without ever sending a payment.
+
+Second, the protocol could be made further private by excluding the MACs in the initiator's bytes.
+This would result in the responder being forced to include `N*M` `encrypted_nonce`s in their
+response (implying the first change above as it would now be too large to include in an onion). This
+would result in the responder not learning whether they were in the initiator's trusted set unless
+they also trusted the initiator, however at the cost of substantial overhead. Because the
+anticipated use cases of this protocol imply mutual trust, this is not considered worth the
+trade-off. Further, this would also present challenges in situations where we only have a full RTT
+available, namely in the BOLT12 `refund` case.
+
+## Reference Implementation
+* LDK: <https://github.com/lightningdevkit/rust-lightning/pull/2829>