From 2e66fcf9598512878ac5eaa37590f99bda29dce2 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Mon, 15 Jan 2024 04:24:48 +0000 Subject: [PATCH 1/5] Add blip-0031, a protocol for mutual message exchange --- blip-0031.md | 134 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 blip-0031.md diff --git a/blip-0031.md b/blip-0031.md new file mode 100644 index 0000000..b97f4f9 --- /dev/null +++ b/blip-0031.md @@ -0,0 +1,134 @@ +``` +bLIP: 31 +Title: Mutual Message Exchange +Status: Active +Author: Matt Corallo +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 || 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 `peer_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 + 16*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, not including the + 16-byte MAC tag. + * MUST set `encrypted_message` to + `AEAD(message, initiator_n ^ messenger_n, PROTO_SALT ^ MASK_B, PROTO_AAD)` + +The initiator (message recipient): + * 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`. + +### Constants + * `H()` is SHA-256 + * `ECDH()` is ECDH using secp256k1 public/secret keys. + * `AEAD()` is the ChaCha20Poly1305 RFC variant authenticated encryption + * `ECDH_SALT` is "Mutual Message Exchange ECDH Result" + * `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: From 1c3c43cef4a361a3ad52bcbf178cb705bf435d6b Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Mon, 15 Jan 2024 20:29:26 +0000 Subject: [PATCH 2/5] f clarify arg order to aead --- blip-0031.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blip-0031.md b/blip-0031.md index b97f4f9..f94a771 100644 --- a/blip-0031.md +++ b/blip-0031.md @@ -75,7 +75,7 @@ The initiator (message recipient): ### Constants * `H()` is SHA-256 * `ECDH()` is ECDH using secp256k1 public/secret keys. - * `AEAD()` is the ChaCha20Poly1305 RFC variant authenticated encryption + * `AEAD(data, key, salt, aad)` is the ChaCha20Poly1305 RFC variant authenticated encryption * `ECDH_SALT` is "Mutual Message Exchange ECDH Result" * `MASK_A` is all 0xff bytes * `MASK_B` is all 0xf0 bytes From 6ba8e68bf23a25be7d3a919ad009b28a62ddd1f4 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Mon, 15 Jan 2024 21:59:18 +0000 Subject: [PATCH 3/5] f document the thing libsecp256k1 is actually doing --- blip-0031.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blip-0031.md b/blip-0031.md index f94a771..0a38514 100644 --- a/blip-0031.md +++ b/blip-0031.md @@ -25,7 +25,7 @@ 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 || ECDH(k, P_i))` for each `P_i` in `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`] From fe5bed9c2bfcccb599b67883349dfb40588842b2 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Tue, 16 Jan 2024 19:59:11 +0000 Subject: [PATCH 4/5] f give users a second key after the handshake so they can msg oob --- blip-0031.md | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/blip-0031.md b/blip-0031.md index 0a38514..976d6dd 100644 --- a/blip-0031.md +++ b/blip-0031.md @@ -54,7 +54,7 @@ The message sender similarly picks a random 32-byte nonce `messenger_n` and resp * [`u16`:`repeated_data_len`] * [`repeated_data_len*byte`:`repeated_data`] * [`u16`:`message_length`] - * [`message_length + 16*byte`:`encrypted_message`] + * [`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 @@ -62,21 +62,32 @@ The message-sender: * 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, not including the - 16-byte MAC tag. - * MUST set `encrypted_message` to - `AEAD(message, initiator_n ^ messenger_n, PROTO_SALT ^ MASK_B, PROTO_AAD)` + * 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 initiator, 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 From 404eab9ef1e4317300b8158d18175afc71d249c6 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Thu, 15 Feb 2024 21:12:23 +0000 Subject: [PATCH 5/5] f typos --- blip-0031.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blip-0031.md b/blip-0031.md index 976d6dd..218effb 100644 --- a/blip-0031.md +++ b/blip-0031.md @@ -46,7 +46,7 @@ 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 `peer_peer_handshake` is decryptable, should send a response including the message. + * 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`] @@ -79,7 +79,7 @@ The initiator (message recipient): * 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 initiator, using the `oob_msg_key` to encrypt and + * MAY exchange further messages with the message-sender, using the `oob_msg_key` to encrypt and authenticate such messages. ### Constants