Skip to content

Commit

Permalink
Bolt 4: add route blinding construction
Browse files Browse the repository at this point in the history
Add specification requirements for creating and using blinded routes.
This commit contains the low-level details of the route blinding scheme,
decoupled from how it can be used by high-level components such as onion
messages or payments.
  • Loading branch information
t-bast committed Mar 28, 2023
1 parent ebd0659 commit 58d8047
Show file tree
Hide file tree
Showing 2 changed files with 287 additions and 0 deletions.
148 changes: 148 additions & 0 deletions 04-onion-routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ A node:
* [Packet Structure](#packet-structure)
* [Payload Format](#payload-format)
* [Basic Multi-Part Payments](#basic-multi-part-payments)
* [Route Blinding](#route-blinding)
* [Accepting and Forwarding a Payment](#accepting-and-forwarding-a-payment)
* [Payload for the Last Node](#payload-for-the-last-node)
* [Non-strict Forwarding](#non-strict-forwarding)
Expand Down Expand Up @@ -352,6 +353,153 @@ otherwise meets the amount criterion (eg. some other failure, or
invoice timeout), however if it were to fulfill only some of them,
intermediary nodes could simply claim the remaining ones.

### Route Blinding

Nodes receiving onion packets may hide their identity from senders by
"blinding" an arbitrary amount of hops at the end of an onion path.

When using route blinding, nodes find a route to themselves from a given
"introduction node". They then use ECDH with each node in that route to create
a "blinded" node ID and an encrypted blob (`encrypted_data`) for each one of
the blinded nodes.

They communicate this blinded route and the encrypted blobs to the sender.
The sender finds a route to the introduction node and extends it with the
blinded route provided by the recipient. The sender includes the encrypted
blobs in the corresponding onion payloads: they allow nodes in the blinded
part of the route to "unblind" the next node and correctly forward the packet.

The `encrypted_data` is a TLV stream, encrypted for a given blinded node, that
may contain the following TLV fields:

1. `tlv_stream`: `encrypted_data_tlv`
2. types:
1. type: 1 (`padding`)
2. data:
* [`...*byte`:`padding`]
1. type: 2 (`short_channel_id`)
2. data:
* [`short_channel_id`:`short_channel_id`]
1. type: 4 (`next_node_id`)
2. data:
* [`point`:`node_id`]
1. type: 6 (`path_id`)
2. data:
* [`...*byte`:`data`]
1. type: 8 (`next_blinding_override`)
2. data:
* [`point`:`blinding`]

#### Requirements

A recipient N(r) creating a blinded route `N(0) -> N(1) -> ... -> N(r)` to itself:

- MUST create a blinded node ID `B(i)` for each node using the following algorithm:
- `e(0) <- {0;1}^256`
- `E(0) = e(0) * G`
- For every node in the route:
- let `N(i) = k(i) * G` be the `node_id` (`k(i)` is `N(i)`'s private key)
- `ss(i) = SHA256(e(i) * N(i)) = SHA256(k(i) * E(i))` (ECDH shared secret known only by `N(r)` and `N(i)`)
- `B(i) = HMAC256("blinded_node_id", ss(i)) * N(i)` (blinded `node_id` for `N(i)`, private key known only by `N(i)`)
- `rho(i) = HMAC256("rho", ss(i))` (key used to encrypt the payload for `N(i)` by `N(r)`)
- `e(i+1) = SHA256(E(i) || ss(i)) * e(i)` (blinding ephemeral private key, only known by `N(r)`)
- `E(i+1) = SHA256(E(i) || ss(i)) * E(i)` (NB: `N(i)` MUST NOT learn `e(i)`)
- If it creates `encrypted_data` payloads for blinded nodes:
- MUST encrypt them with ChaCha20-Poly1305 using the `rho(i)` key and an all-zero nonce
- MAY store private data in the `path_id` of the payload to itself to verify
that the route is used in the right context and was created by them
- SHOULD add padding data to ensure all `encrypted_data` have the same length
- MUST communicate the blinded node IDs `B(i)` and `encrypted_data(i)` to the sender
- MUST communicate the real node ID of the introduction point `N(0)` to the sender
- MUST communicate the first blinding ephemeral key `E(0)` to the sender

The sender:

- MUST provide the `encrypted_data` field for each node in the blinded route
in their respective onion payloads
- MUST communicate `E(0)` to the introduction point (e.g. in the onion payload
for that node or in a tlv field of the lightning message)

The introduction point:

- MUST compute:
- `ss(0) = SHA256(k(0) * E(0))` (standard ECDH)
- `rho(0) = HMAC256("rho", ss(0))`
- `E(1) = SHA256(E(0) || ss(0)) * E(0)`
- If an `encrypted_data` field is provided:
- MUST decrypt it using `rho(0)`
- MUST use the decrypted fields to locate the next node
- If `encrypted_data` contains a `next_blinding_override`:
- MUST use it as the next blinding point instead of `E(1)`
- Otherwise:
- MUST use `E(1)` as the next blinding point
- MUST forward the onion and include the next blinding point in the lightning
message for the next node

An intermediate node in the blinded route:

- MUST compute:
- `ss(i) = SHA256(k(i) * E(i))` (standard ECDH)
- `b(i) = HMAC256("blinded_node_id", ss(i)) * k(i)`
- `rho(i) = HMAC256("rho", ss(i))`
- `E(i+1) = SHA256(E(i) || ss(i)) * E(i)`
- MUST use `b(i)` instead of its private key `k(i)` to decrypt the onion. Note
that the node may instead tweak the onion ephemeral key with
`HMAC256("blinded_node_id", ss(i))` which achieves the same result.
- If an `encrypted_data` field is provided:
- MUST decrypt it using `rho(i)`
- MUST use the decrypted fields to locate the next node
- If `encrypted_data` contains a `next_blinding_override`:
- MUST use it as the next blinding point instead of `E(i+1)`
- Otherwise:
- MUST use `E(i+1)` as the next blinding point
- MUST forward the onion and include the next blinding point in the lightning
message for the next node

The final recipient:

- MUST compute:
- `ss(r) = SHA256(k(r) * E(r))` (standard ECDH)
- `b(r) = HMAC256("blinded_node_id", ss(r)) * k(r)`
- `rho(r) = HMAC256("rho", ss(r))`
- If an `encrypted_data` field is provided:
- MUST decrypt it using `rho(r)`
- MUST ignore the message if the `path_id` does not match the blinded route it
created

#### Rationale

Route blinding is a lightweight technique to provide recipient anonymity.
It's more flexible than rendezvous routing because it simply replaces the public
keys of the nodes in the route with random public keys while letting senders
choose what data they put in the onion for each hop. Blinded routes are also
reusable in some cases (e.g. onion messages).

Each node in the blinded route needs to receive `E(i)` to be able to decrypt
the onion and the `encrypted_data` payload. Protocols that use route blinding
must specify how this value is propagated between nodes.

When concatenating two blinded routes generated by different nodes, the
last node of the first route needs to know the first `blinding_point` of the
second route: the `next_blinding_override` field must be used to transmit this
information.

The final recipient must verify that the blinded route is used in the right
context (e.g. for a specific payment) and was created by them. Otherwise a
malicious sender could create different blinded routes to all the nodes that
they suspect could be the real recipient and try them until one accepts the
message. The recipient can protect against that by storing `E(r)` and the
context (e.g. a `payment_hash`), and verifying that they match when receiving
the onion. Otherwise, to avoid additional storage cost, it can put some private
context information in the `path_id` field (e.g. the `payment_preimage`) and
verify that when receiving the onion. Note that it's important to use private
information in that case, that senders cannot have access to.

The `padding` field can be used to ensure that all `encrypted_data` have the
same length. It's particularly useful when adding dummy hops at the end of a
blinded route, to prevent the sender from figuring out which node is the final
recipient.

# Accepting and Forwarding a Payment

Once a node has decoded the payload it either accepts the payment locally, or forwards it to the peer indicated as the next hop in the payload.
Expand Down
139 changes: 139 additions & 0 deletions bolt04/route-blinding-test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
{
"comment": "test vector for using blinded routes",
"generate": {
"comment": "This section contains test data for creating a blinded route. This route is the concatenation of two blinded routes: one from Dave to Eve and one from Bob to Carol.",
"hops": [
{
"comment": "Bob creates a Bob -> Carol route with the following session_key and concatenates it with the Dave -> Eve route.",
"session_key": "0202020202020202020202020202020202020202020202020202020202020202",
"alias": "Bob",
"node_id": "0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c",
"tlvs": {
"padding": "00000000000000000000000000000000",
"short_channel_id": "0x0x42",
"next_node_id": "027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007",
"unknown_tag_65001": "123456"
},
"encoded_tlvs": "0110000000000000000000000000000000000208000000000000002a0421027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007fdfde903123456",
"ephemeral_privkey": "0202020202020202020202020202020202020202020202020202020202020202",
"ephemeral_pubkey": "024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766",
"shared_secret": "76771bab0cc3d0de6e6f60147fd7c9c7249a5ced3d0612bdfaeec3b15452229d",
"rho": "ba217b23c0978d84c4a19be8a9ff64bc1b40ed0d7ecf59521567a5b3a9a1dd48",
"encrypted_data": "cd4b00ff9c09ed28102b210ac73aa12d63e90a5acebc496c49f57c639e098acbaec5b5ffb8592b07bdb6665ccb56f1258ab1857383f6542c8371dcee568a0a35a218288814849db13ce6f84a464fa517d9e1684333e3",
"blinded_node_id": "03da173ad2aee2f701f17e59fbd16cb708906d69838a5f088e8123fb36e89a2c25"
},
{
"comment": "Notice the next_blinding_override tlv in Carol's payload, indicating that Bob concatenated his route with another blinded route starting at Dave.",
"alias": "Carol",
"node_id": "027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007",
"tlvs": {
"next_node_id": "032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991",
"next_blinding_override": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f"
},
"encoded_tlvs": "0421032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e6686809910821031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f",
"ephemeral_privkey": "0a2aa791ac81265c139237b2b84564f6000b1d4d0e68d4b9cc97c5536c9b61c1",
"ephemeral_pubkey": "034e09f450a80c3d252b258aba0a61215bf60dda3b0dc78ffb0736ea1259dfd8a0",
"shared_secret": "dc91516ec6b530a3d641c01f29b36ed4dc29a74e063258278c0eeed50313d9b8",
"rho": "d1e62bae1a8e169da08e6204997b60b1a7971e0f246814c648125c35660f5416",
"encrypted_data": "ca26157e44ab01e82becf86497e1d05ad3e70903d22721210af41d791bf406873024d95b7a1ad128b2526932febfeeab237000563c1f33c78530b3880f8407326eef8bc004932b22323d13343ef740019c08e538e5c5",
"blinded_node_id": "02e466727716f044290abf91a14a6d90e87487da160c2a3cbd0d465d7a78eb83a7"
},
{
"comment": "Eve creates a Dave -> Eve blinded route using the following session_key.",
"session_key": "0101010101010101010101010101010101010101010101010101010101010101",
"alias": "Dave",
"node_id": "032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991",
"tlvs": {
"padding": "0000000000000000000000000000000000000000000000",
"short_channel_id": "0x0x561",
"next_node_id": "02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145"
},
"encoded_tlvs": "0117000000000000000000000000000000000000000000000002080000000000000231042102edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145",
"ephemeral_privkey": "0101010101010101010101010101010101010101010101010101010101010101",
"ephemeral_pubkey": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f",
"shared_secret": "dc46f3d1d99a536300f17bc0512376cc24b9502c5d30144674bfaa4b923d9057",
"rho": "393aa55d35c9e207a8f28180b81628a31dff558c84959cdc73130f8c321d6a06",
"encrypted_data": "0f94a72cff3b64a3d6e1e4903cf8c8b0a17144aeb249dcb86563a5ee1f679ee8db3c6719bd4364f469aa5fea76ffdc49543d568a707ab73a3e855b25ca585bf12c9d5c9cb6c5c10374a4a66d95aeeea4fe146d0c2754",
"blinded_node_id": "036861b366f284f0a11738ffbf7eda46241a8977592878fe3175ae1d1e4754eccf"
},
{
"comment": "Eve is the final recipient, so she included a path_id in her own payload to verify that the route is used when she expects it.",
"alias": "Eve",
"node_id": "02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145",
"tlvs": {
"padding": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"path_id": "00112233445566778899aabbccddeeff",
"unknown_tag_65535": "06c1"
},
"encoded_tlvs": "012c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000061000112233445566778899aabbccddeefffdffff0206c1",
"ephemeral_privkey": "62e8bcd6b5f7affe29bec4f0515aab2eebd1ce848f4746a9638aa14e3024fb1b",
"ephemeral_pubkey": "03e09038ee76e50f444b19abf0a555e8697e035f62937168b80adf0931b31ce52a",
"shared_secret": "352a706b194c2b6d0a04ba1f617383fb816dc5f8f9ac0b60dd19c9ae3b517289",
"rho": "719d0307340b1c68b79865111f0de6e97b093a30bc603cebd1beb9eef116f2d8",
"encrypted_data": "da2c7e5f7881219884beae6ae68971de73bab4c3055d9865b1afb60722a63a7e4ea796de84fc9af674952e900ff518ed6b3640a7e47b5f3e4fbce5fab87e47a11d84c66d1234f1cec1da2f56b72b64896509aef9b754",
"blinded_node_id": "021982a48086cb8984427d3727fe35a03d396b234f0701f5249daa12e8105c8dae"
}
]
},
"route": {
"comment": "This section contains the resulting blinded route, which can then be used inside onion messages or payments.",
"introduction_node_id": "0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c",
"blinding": "024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766",
"hops": [
{
"blinded_node_id": "03da173ad2aee2f701f17e59fbd16cb708906d69838a5f088e8123fb36e89a2c25",
"encrypted_data": "cd4b00ff9c09ed28102b210ac73aa12d63e90a5acebc496c49f57c639e098acbaec5b5ffb8592b07bdb6665ccb56f1258ab1857383f6542c8371dcee568a0a35a218288814849db13ce6f84a464fa517d9e1684333e3"
},
{
"blinded_node_id": "02e466727716f044290abf91a14a6d90e87487da160c2a3cbd0d465d7a78eb83a7",
"encrypted_data": "ca26157e44ab01e82becf86497e1d05ad3e70903d22721210af41d791bf406873024d95b7a1ad128b2526932febfeeab237000563c1f33c78530b3880f8407326eef8bc004932b22323d13343ef740019c08e538e5c5"
},
{
"blinded_node_id": "036861b366f284f0a11738ffbf7eda46241a8977592878fe3175ae1d1e4754eccf",
"encrypted_data": "0f94a72cff3b64a3d6e1e4903cf8c8b0a17144aeb249dcb86563a5ee1f679ee8db3c6719bd4364f469aa5fea76ffdc49543d568a707ab73a3e855b25ca585bf12c9d5c9cb6c5c10374a4a66d95aeeea4fe146d0c2754"
},
{
"blinded_node_id": "021982a48086cb8984427d3727fe35a03d396b234f0701f5249daa12e8105c8dae",
"encrypted_data": "da2c7e5f7881219884beae6ae68971de73bab4c3055d9865b1afb60722a63a7e4ea796de84fc9af674952e900ff518ed6b3640a7e47b5f3e4fbce5fab87e47a11d84c66d1234f1cec1da2f56b72b64896509aef9b754"
}
]
},
"unblind": {
"comment": "This section contains test data for unblinding the route at each intermediate hop.",
"hops": [
{
"alias": "Bob",
"node_privkey": "4242424242424242424242424242424242424242424242424242424242424242",
"ephemeral_pubkey": "024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766",
"blinded_privkey": "d12fec0332c3e9d224789a17ebd93595f37d37bd8ef8bd3d2e6ce50acb9e554f",
"decrypted_data": "0110000000000000000000000000000000000208000000000000002a0421027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007fdfde903123456",
"next_ephemeral_pubkey": "034e09f450a80c3d252b258aba0a61215bf60dda3b0dc78ffb0736ea1259dfd8a0"
},
{
"alias": "Carol",
"node_privkey": "4343434343434343434343434343434343434343434343434343434343434343",
"ephemeral_pubkey": "034e09f450a80c3d252b258aba0a61215bf60dda3b0dc78ffb0736ea1259dfd8a0",
"blinded_privkey": "bfa697fbbc8bbc43ca076e6dd60d306038a32af216b9dc6fc4e59e5ae28823c1",
"decrypted_data": "0421032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e6686809910821031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f",
"next_ephemeral_pubkey": "03af5ccc91851cb294e3a364ce63347709a08cdffa58c672e9a5c587ddd1bbca60",
"next_ephemeral_pubkey_override": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f"
},
{
"alias": "Dave",
"node_privkey": "4444444444444444444444444444444444444444444444444444444444444444",
"ephemeral_pubkey": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f",
"blinded_privkey": "cebc115c7fce4c295dc396dea6c79115b289b8ceeceea2ed61cf31428d88fc4e",
"decrypted_data": "0117000000000000000000000000000000000000000000000002080000000000000231042102edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145",
"next_ephemeral_pubkey": "03e09038ee76e50f444b19abf0a555e8697e035f62937168b80adf0931b31ce52a"
},
{
"alias": "Eve",
"node_privkey": "4545454545454545454545454545454545454545454545454545454545454545",
"ephemeral_pubkey": "03e09038ee76e50f444b19abf0a555e8697e035f62937168b80adf0931b31ce52a",
"blinded_privkey": "ff4e07da8d92838bedd019ce532eb990ed73b574e54a67862a1df81b40c0d2af",
"decrypted_data": "012c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000061000112233445566778899aabbccddeefffdffff0206c1",
"next_ephemeral_pubkey": "038fc6859a402b96ce4998c537c823d6ab94d1598fca02c788ba5dd79fbae83589"
}
]
}
}

0 comments on commit 58d8047

Please sign in to comment.