Skip to content

Commit 0211daa

Browse files
authored
Merge pull request #2412 from valentinewallace/2023-07-construct-blinded-paths
Add API for constructing blinded payment paths
2 parents 0c25046 + ea84f2a commit 0211daa

File tree

13 files changed

+450
-209
lines changed

13 files changed

+450
-209
lines changed

lightning/src/blinded_path/message.rs

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey};
2+
3+
use crate::blinded_path::{BlindedHop, BlindedPath};
4+
use crate::blinded_path::utils;
5+
use crate::io;
6+
use crate::io::Cursor;
7+
use crate::ln::onion_utils;
8+
use crate::onion_message::ControlTlvs;
9+
use crate::prelude::*;
10+
use crate::sign::{NodeSigner, Recipient};
11+
use crate::util::chacha20poly1305rfc::ChaChaPolyReadAdapter;
12+
use crate::util::ser::{FixedLengthReader, LengthReadableArgs, Writeable, Writer};
13+
14+
use core::mem;
15+
use core::ops::Deref;
16+
17+
/// TLVs to encode in an intermediate onion message packet's hop data. When provided in a blinded
18+
/// route, they are encoded into [`BlindedHop::encrypted_payload`].
19+
pub(crate) struct ForwardTlvs {
20+
/// The node id of the next hop in the onion message's path.
21+
pub(crate) next_node_id: PublicKey,
22+
/// Senders to a blinded path use this value to concatenate the route they find to the
23+
/// introduction node with the blinded path.
24+
pub(crate) next_blinding_override: Option<PublicKey>,
25+
}
26+
27+
/// Similar to [`ForwardTlvs`], but these TLVs are for the final node.
28+
pub(crate) struct ReceiveTlvs {
29+
/// If `path_id` is `Some`, it is used to identify the blinded path that this onion message is
30+
/// sending to. This is useful for receivers to check that said blinded path is being used in
31+
/// the right context.
32+
pub(crate) path_id: Option<[u8; 32]>,
33+
}
34+
35+
impl Writeable for ForwardTlvs {
36+
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
37+
// TODO: write padding
38+
encode_tlv_stream!(writer, {
39+
(4, self.next_node_id, required),
40+
(8, self.next_blinding_override, option)
41+
});
42+
Ok(())
43+
}
44+
}
45+
46+
impl Writeable for ReceiveTlvs {
47+
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
48+
// TODO: write padding
49+
encode_tlv_stream!(writer, {
50+
(6, self.path_id, option),
51+
});
52+
Ok(())
53+
}
54+
}
55+
56+
/// Construct blinded onion message hops for the given `unblinded_path`.
57+
pub(super) fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
58+
secp_ctx: &Secp256k1<T>, unblinded_path: &[PublicKey], session_priv: &SecretKey
59+
) -> Result<Vec<BlindedHop>, secp256k1::Error> {
60+
let blinded_tlvs = unblinded_path.iter()
61+
.skip(1) // The first node's TLVs contains the next node's pubkey
62+
.map(|pk| {
63+
ControlTlvs::Forward(ForwardTlvs { next_node_id: *pk, next_blinding_override: None })
64+
})
65+
.chain(core::iter::once(ControlTlvs::Receive(ReceiveTlvs { path_id: None })));
66+
67+
utils::construct_blinded_hops(secp_ctx, unblinded_path.iter(), blinded_tlvs, session_priv)
68+
}
69+
70+
// Advance the blinded onion message path by one hop, so make the second hop into the new
71+
// introduction node.
72+
pub(crate) fn advance_path_by_one<NS: Deref, T: secp256k1::Signing + secp256k1::Verification>(
73+
path: &mut BlindedPath, node_signer: &NS, secp_ctx: &Secp256k1<T>
74+
) -> Result<(), ()> where NS::Target: NodeSigner {
75+
let control_tlvs_ss = node_signer.ecdh(Recipient::Node, &path.blinding_point, None)?;
76+
let rho = onion_utils::gen_rho_from_shared_secret(&control_tlvs_ss.secret_bytes());
77+
let encrypted_control_tlvs = path.blinded_hops.remove(0).encrypted_payload;
78+
let mut s = Cursor::new(&encrypted_control_tlvs);
79+
let mut reader = FixedLengthReader::new(&mut s, encrypted_control_tlvs.len() as u64);
80+
match ChaChaPolyReadAdapter::read(&mut reader, rho) {
81+
Ok(ChaChaPolyReadAdapter { readable: ControlTlvs::Forward(ForwardTlvs {
82+
mut next_node_id, next_blinding_override,
83+
})}) => {
84+
let mut new_blinding_point = match next_blinding_override {
85+
Some(blinding_point) => blinding_point,
86+
None => {
87+
onion_utils::next_hop_pubkey(secp_ctx, path.blinding_point,
88+
control_tlvs_ss.as_ref()).map_err(|_| ())?
89+
}
90+
};
91+
mem::swap(&mut path.blinding_point, &mut new_blinding_point);
92+
mem::swap(&mut path.introduction_node_id, &mut next_node_id);
93+
Ok(())
94+
},
95+
_ => Err(())
96+
}
97+
}

lightning/src/blinded_path/mod.rs

+30-121
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,17 @@
99

1010
//! Creating blinded paths and related utilities live here.
1111
12+
pub mod payment;
13+
pub(crate) mod message;
1214
pub(crate) mod utils;
1315

1416
use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey};
1517

16-
use crate::sign::{EntropySource, NodeSigner, Recipient};
17-
use crate::onion_message::ControlTlvs;
18+
use crate::sign::EntropySource;
1819
use crate::ln::msgs::DecodeError;
19-
use crate::ln::onion_utils;
20-
use crate::util::chacha20poly1305rfc::{ChaChaPolyReadAdapter, ChaChaPolyWriteAdapter};
21-
use crate::util::ser::{FixedLengthReader, LengthReadableArgs, Readable, VecWriter, Writeable, Writer};
20+
use crate::util::ser::{Readable, Writeable, Writer};
2221

23-
use core::mem;
24-
use core::ops::Deref;
25-
use crate::io::{self, Cursor};
22+
use crate::io;
2623
use crate::prelude::*;
2724

2825
/// Onion messages and payments can be sent and received to blinded paths, which serve to hide the
@@ -44,13 +41,14 @@ pub struct BlindedPath {
4441
pub blinded_hops: Vec<BlindedHop>,
4542
}
4643

47-
/// Used to construct the blinded hops portion of a blinded path. These hops cannot be identified
48-
/// by outside observers and thus can be used to hide the identity of the recipient.
44+
/// An encrypted payload and node id corresponding to a hop in a payment or onion message path, to
45+
/// be encoded in the sender's onion packet. These hops cannot be identified by outside observers
46+
/// and thus can be used to hide the identity of the recipient.
4947
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
5048
pub struct BlindedHop {
51-
/// The blinded node id of this hop in a blinded path.
49+
/// The blinded node id of this hop in a [`BlindedPath`].
5250
pub blinded_node_id: PublicKey,
53-
/// The encrypted payload intended for this hop in a blinded path.
51+
/// The encrypted payload intended for this hop in a [`BlindedPath`].
5452
// The node sending to this blinded path will later encode this payload into the onion packet for
5553
// this hop.
5654
pub encrypted_payload: Vec<u8>,
@@ -73,81 +71,30 @@ impl BlindedPath {
7371
Ok(BlindedPath {
7472
introduction_node_id,
7573
blinding_point: PublicKey::from_secret_key(secp_ctx, &blinding_secret),
76-
blinded_hops: blinded_message_hops(secp_ctx, node_pks, &blinding_secret).map_err(|_| ())?,
74+
blinded_hops: message::blinded_hops(secp_ctx, node_pks, &blinding_secret).map_err(|_| ())?,
7775
})
7876
}
7977

80-
// Advance the blinded onion message path by one hop, so make the second hop into the new
81-
// introduction node.
82-
pub(super) fn advance_message_path_by_one<NS: Deref, T: secp256k1::Signing + secp256k1::Verification>
83-
(&mut self, node_signer: &NS, secp_ctx: &Secp256k1<T>) -> Result<(), ()>
84-
where NS::Target: NodeSigner
85-
{
86-
let control_tlvs_ss = node_signer.ecdh(Recipient::Node, &self.blinding_point, None)?;
87-
let rho = onion_utils::gen_rho_from_shared_secret(&control_tlvs_ss.secret_bytes());
88-
let encrypted_control_tlvs = self.blinded_hops.remove(0).encrypted_payload;
89-
let mut s = Cursor::new(&encrypted_control_tlvs);
90-
let mut reader = FixedLengthReader::new(&mut s, encrypted_control_tlvs.len() as u64);
91-
match ChaChaPolyReadAdapter::read(&mut reader, rho) {
92-
Ok(ChaChaPolyReadAdapter { readable: ControlTlvs::Forward(ForwardTlvs {
93-
mut next_node_id, next_blinding_override,
94-
})}) => {
95-
let mut new_blinding_point = match next_blinding_override {
96-
Some(blinding_point) => blinding_point,
97-
None => {
98-
onion_utils::next_hop_pubkey(secp_ctx, self.blinding_point,
99-
control_tlvs_ss.as_ref()).map_err(|_| ())?
100-
}
101-
};
102-
mem::swap(&mut self.blinding_point, &mut new_blinding_point);
103-
mem::swap(&mut self.introduction_node_id, &mut next_node_id);
104-
Ok(())
105-
},
106-
_ => Err(())
107-
}
108-
}
109-
}
110-
111-
/// Construct blinded onion message hops for the given `unblinded_path`.
112-
fn blinded_message_hops<T: secp256k1::Signing + secp256k1::Verification>(
113-
secp_ctx: &Secp256k1<T>, unblinded_path: &[PublicKey], session_priv: &SecretKey
114-
) -> Result<Vec<BlindedHop>, secp256k1::Error> {
115-
let mut blinded_hops = Vec::with_capacity(unblinded_path.len());
116-
117-
let mut prev_ss_and_blinded_node_id = None;
118-
utils::construct_keys_callback(secp_ctx, unblinded_path, None, session_priv, |blinded_node_id, _, _, encrypted_payload_ss, unblinded_pk, _| {
119-
if let Some((prev_ss, prev_blinded_node_id)) = prev_ss_and_blinded_node_id {
120-
if let Some(pk) = unblinded_pk {
121-
let payload = ForwardTlvs {
122-
next_node_id: pk,
123-
next_blinding_override: None,
124-
};
125-
blinded_hops.push(BlindedHop {
126-
blinded_node_id: prev_blinded_node_id,
127-
encrypted_payload: encrypt_payload(payload, prev_ss),
128-
});
129-
} else { debug_assert!(false); }
130-
}
131-
prev_ss_and_blinded_node_id = Some((encrypted_payload_ss, blinded_node_id));
132-
})?;
133-
134-
if let Some((final_ss, final_blinded_node_id)) = prev_ss_and_blinded_node_id {
135-
let final_payload = ReceiveTlvs { path_id: None };
136-
blinded_hops.push(BlindedHop {
137-
blinded_node_id: final_blinded_node_id,
138-
encrypted_payload: encrypt_payload(final_payload, final_ss),
139-
});
140-
} else { debug_assert!(false) }
141-
142-
Ok(blinded_hops)
143-
}
78+
/// Create a blinded path for a payment, to be forwarded along `path`. The last node
79+
/// in `path` will be the destination node.
80+
///
81+
/// Errors if `path` is empty or a node id in `path` is invalid.
82+
// TODO: make all payloads the same size with padding + add dummy hops
83+
pub fn new_for_payment<ES: EntropySource, T: secp256k1::Signing + secp256k1::Verification>(
84+
intermediate_nodes: &[(PublicKey, payment::ForwardTlvs)], payee_node_id: PublicKey,
85+
payee_tlvs: payment::ReceiveTlvs, entropy_source: &ES, secp_ctx: &Secp256k1<T>
86+
) -> Result<Self, ()> {
87+
let blinding_secret_bytes = entropy_source.get_secure_random_bytes();
88+
let blinding_secret = SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted");
14489

145-
/// Encrypt TLV payload to be used as a [`BlindedHop::encrypted_payload`].
146-
fn encrypt_payload<P: Writeable>(payload: P, encrypted_tlvs_ss: [u8; 32]) -> Vec<u8> {
147-
let mut writer = VecWriter(Vec::new());
148-
let write_adapter = ChaChaPolyWriteAdapter::new(encrypted_tlvs_ss, &payload);
149-
write_adapter.write(&mut writer).expect("In-memory writes cannot fail");
150-
writer.0
90+
Ok(BlindedPath {
91+
introduction_node_id: intermediate_nodes.first().map_or(payee_node_id, |n| n.0),
92+
blinding_point: PublicKey::from_secret_key(secp_ctx, &blinding_secret),
93+
blinded_hops: payment::blinded_hops(
94+
secp_ctx, intermediate_nodes, payee_node_id, payee_tlvs, &blinding_secret
95+
).map_err(|_| ())?,
96+
})
97+
}
15198
}
15299

153100
impl Writeable for BlindedPath {
@@ -185,41 +132,3 @@ impl_writeable!(BlindedHop, {
185132
encrypted_payload
186133
});
187134

188-
/// TLVs to encode in an intermediate onion message packet's hop data. When provided in a blinded
189-
/// route, they are encoded into [`BlindedHop::encrypted_payload`].
190-
pub(crate) struct ForwardTlvs {
191-
/// The node id of the next hop in the onion message's path.
192-
pub(super) next_node_id: PublicKey,
193-
/// Senders to a blinded path use this value to concatenate the route they find to the
194-
/// introduction node with the blinded path.
195-
pub(super) next_blinding_override: Option<PublicKey>,
196-
}
197-
198-
/// Similar to [`ForwardTlvs`], but these TLVs are for the final node.
199-
pub(crate) struct ReceiveTlvs {
200-
/// If `path_id` is `Some`, it is used to identify the blinded path that this onion message is
201-
/// sending to. This is useful for receivers to check that said blinded path is being used in
202-
/// the right context.
203-
pub(super) path_id: Option<[u8; 32]>,
204-
}
205-
206-
impl Writeable for ForwardTlvs {
207-
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
208-
// TODO: write padding
209-
encode_tlv_stream!(writer, {
210-
(4, self.next_node_id, required),
211-
(8, self.next_blinding_override, option)
212-
});
213-
Ok(())
214-
}
215-
}
216-
217-
impl Writeable for ReceiveTlvs {
218-
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
219-
// TODO: write padding
220-
encode_tlv_stream!(writer, {
221-
(6, self.path_id, option),
222-
});
223-
Ok(())
224-
}
225-
}

0 commit comments

Comments
 (0)