Skip to content

Commit c10a0af

Browse files
committed
Accept blinded paths built by a phantom node participant
In the next commit we'll add support for building a BOLT 12 offer which can be paid to any one of a number of participant nodes. Here we add support for validating blinded paths as coming from one of the participating nodes by deriving a new key as a part of the `ExpandedKey`. We keep this separate from the existing `ReceiveAuthKey` which is node-specific to ensure that we only allow this key to be used for blinded payment paths and contexts in `invoice_request` messages. This ensures that normal onion messages are still tied to specific nodes. Note that we will not yet use the blinded payment path phantom support which requires additional future work. However, allowing them to be authenticated in a phantom configuration should allow for compatibility across versions once the building logic lands.
1 parent 14a4740 commit c10a0af

File tree

8 files changed

+124
-77
lines changed

8 files changed

+124
-77
lines changed

fuzz/src/onion_message.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ impl NodeSigner for KeyProvider {
260260
}
261261

262262
fn get_expanded_key(&self) -> ExpandedKey {
263-
unreachable!()
263+
ExpandedKey::new([42; 32])
264264
}
265265

266266
fn sign_invoice(

lightning/src/blinded_path/payment.rs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey};
1414

1515
use crate::blinded_path::utils::{self, BlindedPathWithPadding};
1616
use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode, NodeIdLookUp};
17-
use crate::crypto::streams::ChaChaDualPolyReadAdapter;
17+
use crate::crypto::streams::{ChaChaTriPolyReadAdapter, TriPolyAADUsed};
1818
use crate::io;
1919
use crate::io::Cursor;
2020
use crate::ln::channel_state::CounterpartyForwardingInfo;
@@ -268,18 +268,20 @@ impl BlindedPaymentPath {
268268
node_signer.ecdh(Recipient::Node, &self.inner_path.blinding_point, None)?;
269269
let rho = onion_utils::gen_rho_from_shared_secret(&control_tlvs_ss.secret_bytes());
270270
let receive_auth_key = node_signer.get_receive_auth_key();
271+
let phantom_auth_key = node_signer.get_expanded_key().phantom_node_blinded_path_key;
272+
let read_arg = (rho, receive_auth_key.0, phantom_auth_key);
273+
271274
let encrypted_control_tlvs =
272275
&self.inner_path.blinded_hops.get(0).ok_or(())?.encrypted_payload;
273276
let mut s = Cursor::new(encrypted_control_tlvs);
274277
let mut reader = FixedLengthReader::new(&mut s, encrypted_control_tlvs.len() as u64);
275-
let ChaChaDualPolyReadAdapter { readable, used_aad } =
276-
ChaChaDualPolyReadAdapter::read(&mut reader, (rho, receive_auth_key.0))
277-
.map_err(|_| ())?;
278-
279-
match (&readable, used_aad) {
280-
(BlindedPaymentTlvs::Forward(_), false)
281-
| (BlindedPaymentTlvs::Dummy(_), true)
282-
| (BlindedPaymentTlvs::Receive(_), true) => Ok((readable, control_tlvs_ss)),
278+
let ChaChaTriPolyReadAdapter { readable, used_aad } =
279+
ChaChaTriPolyReadAdapter::read(&mut reader, read_arg).map_err(|_| ())?;
280+
281+
match (&readable, used_aad == TriPolyAADUsed::None) {
282+
(BlindedPaymentTlvs::Forward(_), true)
283+
| (BlindedPaymentTlvs::Dummy(_), false)
284+
| (BlindedPaymentTlvs::Receive(_), false) => Ok((readable, control_tlvs_ss)),
283285
_ => Err(()),
284286
}
285287
}

lightning/src/crypto/streams.rs

Lines changed: 45 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ impl<'a, T: Writeable> Writeable for ChaChaPolyWriteAdapter<'a, T> {
5858
}
5959

6060
/// Encrypts the provided plaintext with the given key using ChaCha20Poly1305 in the modified
61-
/// with-AAD form used in [`ChaChaDualPolyReadAdapter`].
61+
/// with-AAD form used in [`ChaChaTriPolyReadAdapter`].
6262
pub(crate) fn chachapoly_encrypt_with_swapped_aad(
6363
mut plaintext: Vec<u8>, key: [u8; 32], aad: [u8; 32],
6464
) -> Vec<u8> {
@@ -84,34 +84,48 @@ pub(crate) fn chachapoly_encrypt_with_swapped_aad(
8484
plaintext
8585
}
8686

87+
#[derive(PartialEq, Eq)]
88+
pub(crate) enum TriPolyAADUsed {
89+
/// No AAD was used.
90+
///
91+
/// The HMAC validated with standard ChaCha20Poly1305.
92+
None,
93+
/// The HMAC vlidated using the first AAD provided.
94+
First,
95+
/// The HMAC vlidated using the second AAD provided.
96+
Second,
97+
}
98+
8799
/// Enables the use of the serialization macros for objects that need to be simultaneously decrypted
88100
/// and deserialized. This allows us to avoid an intermediate Vec allocation.
89101
///
90-
/// This variant of [`ChaChaPolyReadAdapter`] calculates Poly1305 tags twice, once using the given
91-
/// key and once with the given 32-byte AAD appended after the encrypted stream, accepting either
92-
/// being correct as sufficient.
102+
/// This variant of [`ChaChaPolyReadAdapter`] calculates Poly1305 tags thrice, once using the given
103+
/// key and once each for the two given 32-byte AADs appended after the encrypted stream, accepting
104+
/// any being correct as sufficient.
93105
///
94-
/// Note that we do *not* use the provided AAD as the standard ChaCha20Poly1305 AAD as that would
106+
/// Note that we do *not* use the provided AADs as the standard ChaCha20Poly1305 AAD as that would
95107
/// require placing it first and prevent us from avoiding redundant Poly1305 rounds. Instead, the
96108
/// ChaCha20Poly1305 MAC check is tweaked to move the AAD to *after* the the contents being
97109
/// checked, effectively treating the contents as the AAD for the AAD-containing MAC but behaving
98110
/// like classic ChaCha20Poly1305 for the non-AAD-containing MAC.
99-
pub(crate) struct ChaChaDualPolyReadAdapter<R: Readable> {
111+
pub(crate) struct ChaChaTriPolyReadAdapter<R: Readable> {
100112
pub readable: R,
101-
pub used_aad: bool,
113+
pub used_aad: TriPolyAADUsed,
102114
}
103115

104-
impl<T: Readable> LengthReadableArgs<([u8; 32], [u8; 32])> for ChaChaDualPolyReadAdapter<T> {
116+
impl<T: Readable> LengthReadableArgs<([u8; 32], [u8; 32], [u8; 32])>
117+
for ChaChaTriPolyReadAdapter<T>
118+
{
105119
// Simultaneously read and decrypt an object from a LengthLimitedRead storing it in
106120
// Self::readable. LengthLimitedRead must be used instead of std::io::Read because we need the
107121
// total length to separate out the tag at the end.
108122
fn read<R: LengthLimitedRead>(
109-
r: &mut R, params: ([u8; 32], [u8; 32]),
123+
r: &mut R, params: ([u8; 32], [u8; 32], [u8; 32]),
110124
) -> Result<Self, DecodeError> {
111125
if r.remaining_bytes() < 16 {
112126
return Err(DecodeError::InvalidValue);
113127
}
114-
let (key, aad) = params;
128+
let (key, aad_a, aad_b) = params;
115129

116130
let mut chacha = ChaCha20::new(&key[..], &[0; 12]);
117131
let mut mac_key = [0u8; 64];
@@ -125,7 +139,7 @@ impl<T: Readable> LengthReadableArgs<([u8; 32], [u8; 32])> for ChaChaDualPolyRea
125139
let decrypted_len = r.remaining_bytes() - 16;
126140
let s = FixedLengthReader::new(r, decrypted_len);
127141
let mut chacha_stream =
128-
ChaChaDualPolyReader { chacha: &mut chacha, poly: &mut mac, read_len: 0, read: s };
142+
ChaChaTriPolyReader { chacha: &mut chacha, poly: &mut mac, read_len: 0, read: s };
129143

130144
let readable: T = Readable::read(&mut chacha_stream)?;
131145
while chacha_stream.read.bytes_remain() {
@@ -142,14 +156,18 @@ impl<T: Readable> LengthReadableArgs<([u8; 32], [u8; 32])> for ChaChaDualPolyRea
142156
mac.input(&[0; 16][0..16 - (read_len % 16)]);
143157
}
144158

145-
let mut mac_aad = mac;
159+
let mut mac_aad_a = mac;
160+
let mut mac_aad_b = mac;
146161

147-
mac_aad.input(&aad[..]);
162+
mac_aad_a.input(&aad_a[..]);
163+
mac_aad_b.input(&aad_b[..]);
148164
// Note that we don't need to pad the AAD since its a multiple of 16 bytes
149165

150166
// For the AAD-containing MAC, swap the AAD and the read data, effectively.
151-
mac_aad.input(&(read_len as u64).to_le_bytes());
152-
mac_aad.input(&32u64.to_le_bytes());
167+
mac_aad_a.input(&(read_len as u64).to_le_bytes());
168+
mac_aad_b.input(&(read_len as u64).to_le_bytes());
169+
mac_aad_a.input(&32u64.to_le_bytes());
170+
mac_aad_b.input(&32u64.to_le_bytes());
153171

154172
// For the non-AAD-containing MAC, leave the data and AAD where they belong.
155173
mac.input(&0u64.to_le_bytes());
@@ -158,23 +176,25 @@ impl<T: Readable> LengthReadableArgs<([u8; 32], [u8; 32])> for ChaChaDualPolyRea
158176
let mut tag = [0 as u8; 16];
159177
r.read_exact(&mut tag)?;
160178
if fixed_time_eq(&mac.result(), &tag) {
161-
Ok(Self { readable, used_aad: false })
162-
} else if fixed_time_eq(&mac_aad.result(), &tag) {
163-
Ok(Self { readable, used_aad: true })
179+
Ok(Self { readable, used_aad: TriPolyAADUsed::None })
180+
} else if fixed_time_eq(&mac_aad_a.result(), &tag) {
181+
Ok(Self { readable, used_aad: TriPolyAADUsed::First })
182+
} else if fixed_time_eq(&mac_aad_b.result(), &tag) {
183+
Ok(Self { readable, used_aad: TriPolyAADUsed::Second })
164184
} else {
165185
return Err(DecodeError::InvalidValue);
166186
}
167187
}
168188
}
169189

170-
struct ChaChaDualPolyReader<'a, R: Read> {
190+
struct ChaChaTriPolyReader<'a, R: Read> {
171191
chacha: &'a mut ChaCha20,
172192
poly: &'a mut Poly1305,
173193
read_len: usize,
174194
pub read: R,
175195
}
176196

177-
impl<'a, R: Read> Read for ChaChaDualPolyReader<'a, R> {
197+
impl<'a, R: Read> Read for ChaChaTriPolyReader<'a, R> {
178198
// Decrypts bytes from Self::read into `dest`.
179199
// After all reads complete, the caller must compare the expected tag with
180200
// the result of `Poly1305::result()`.
@@ -349,15 +369,15 @@ mod tests {
349369
}
350370

351371
#[test]
352-
fn short_read_chacha_dual_read_adapter() {
353-
// Previously, if we attempted to read from a ChaChaDualPolyReadAdapter but the object
372+
fn short_read_chacha_tri_read_adapter() {
373+
// Previously, if we attempted to read from a ChaChaTriPolyReadAdapter but the object
354374
// being read is shorter than the available buffer while the buffer passed to
355-
// ChaChaDualPolyReadAdapter itself always thinks it has room, we'd end up
375+
// ChaChaTriPolyReadAdapter itself always thinks it has room, we'd end up
356376
// infinite-looping as we didn't handle `Read::read`'s 0 return values at EOF.
357377
let mut stream = &[0; 1024][..];
358378
let mut too_long_stream = FixedLengthReader::new(&mut stream, 2048);
359-
let keys = ([42; 32], [99; 32]);
360-
let res = super::ChaChaDualPolyReadAdapter::<u8>::read(&mut too_long_stream, keys);
379+
let keys = ([42; 32], [98; 32], [99; 32]);
380+
let res = super::ChaChaTriPolyReadAdapter::<u8>::read(&mut too_long_stream, keys);
361381
match res {
362382
Ok(_) => panic!(),
363383
Err(e) => assert_eq!(e, DecodeError::ShortRead),

lightning/src/ln/blinded_payment_tests.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1696,7 +1696,7 @@ fn route_blinding_spec_test_vector() {
16961696
}
16971697
Ok(SharedSecret::new(other_key, &node_secret))
16981698
}
1699-
fn get_expanded_key(&self) -> ExpandedKey { unreachable!() }
1699+
fn get_expanded_key(&self) -> ExpandedKey { ExpandedKey::new([42; 32]) }
17001700
fn get_node_id(&self, _recipient: Recipient) -> Result<PublicKey, ()> { unreachable!() }
17011701
fn sign_invoice(
17021702
&self, _invoice: &RawBolt11Invoice, _recipient: Recipient,
@@ -2011,7 +2011,7 @@ fn test_trampoline_inbound_payment_decoding() {
20112011
}
20122012
Ok(SharedSecret::new(other_key, &node_secret))
20132013
}
2014-
fn get_expanded_key(&self) -> ExpandedKey { unreachable!() }
2014+
fn get_expanded_key(&self) -> ExpandedKey { ExpandedKey::new([42; 32]) }
20152015
fn get_node_id(&self, _recipient: Recipient) -> Result<PublicKey, ()> { unreachable!() }
20162016
fn sign_invoice(
20172017
&self, _invoice: &RawBolt11Invoice, _recipient: Recipient,

lightning/src/ln/msgs.rs

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ use core::str::FromStr;
5656
#[cfg(feature = "std")]
5757
use std::net::SocketAddr;
5858

59-
use crate::crypto::streams::ChaChaDualPolyReadAdapter;
59+
use crate::crypto::streams::{ChaChaTriPolyReadAdapter, TriPolyAADUsed};
6060
use crate::util::base32;
6161
use crate::util::logger;
6262
use crate::util::ser::{
@@ -3924,10 +3924,13 @@ impl<NS: NodeSigner> ReadableArgs<(Option<PublicKey>, NS)> for InboundOnionPaylo
39243924
.map_err(|_| DecodeError::InvalidValue)?;
39253925
let rho = onion_utils::gen_rho_from_shared_secret(&enc_tlvs_ss.secret_bytes());
39263926
let receive_auth_key = node_signer.get_receive_auth_key();
3927+
let phantom_auth_key = node_signer.get_expanded_key().phantom_node_blinded_path_key;
3928+
let read_args = (rho, receive_auth_key.0, phantom_auth_key);
3929+
39273930
let mut s = Cursor::new(&enc_tlvs);
39283931
let mut reader = FixedLengthReader::new(&mut s, enc_tlvs.len() as u64);
3929-
match ChaChaDualPolyReadAdapter::read(&mut reader, (rho, receive_auth_key.0))? {
3930-
ChaChaDualPolyReadAdapter {
3932+
match ChaChaTriPolyReadAdapter::read(&mut reader, read_args)? {
3933+
ChaChaTriPolyReadAdapter {
39313934
readable:
39323935
BlindedPaymentTlvs::Forward(ForwardTlvs {
39333936
short_channel_id,
@@ -3942,7 +3945,7 @@ impl<NS: NodeSigner> ReadableArgs<(Option<PublicKey>, NS)> for InboundOnionPaylo
39423945
|| cltv_value.is_some() || total_msat.is_some()
39433946
|| keysend_preimage.is_some()
39443947
|| invoice_request.is_some()
3945-
|| used_aad
3948+
|| used_aad != TriPolyAADUsed::None
39463949
{
39473950
return Err(DecodeError::InvalidValue);
39483951
}
@@ -3955,7 +3958,7 @@ impl<NS: NodeSigner> ReadableArgs<(Option<PublicKey>, NS)> for InboundOnionPaylo
39553958
next_blinding_override,
39563959
}))
39573960
},
3958-
ChaChaDualPolyReadAdapter {
3961+
ChaChaTriPolyReadAdapter {
39593962
readable:
39603963
BlindedPaymentTlvs::Dummy(DummyTlvs { payment_relay, payment_constraints }),
39613964
used_aad,
@@ -3964,7 +3967,7 @@ impl<NS: NodeSigner> ReadableArgs<(Option<PublicKey>, NS)> for InboundOnionPaylo
39643967
|| cltv_value.is_some() || total_msat.is_some()
39653968
|| keysend_preimage.is_some()
39663969
|| invoice_request.is_some()
3967-
|| !used_aad
3970+
|| used_aad == TriPolyAADUsed::None
39683971
{
39693972
return Err(DecodeError::InvalidValue);
39703973
}
@@ -3974,11 +3977,11 @@ impl<NS: NodeSigner> ReadableArgs<(Option<PublicKey>, NS)> for InboundOnionPaylo
39743977
intro_node_blinding_point,
39753978
}))
39763979
},
3977-
ChaChaDualPolyReadAdapter {
3980+
ChaChaTriPolyReadAdapter {
39783981
readable: BlindedPaymentTlvs::Receive(receive_tlvs),
39793982
used_aad,
39803983
} => {
3981-
if !used_aad {
3984+
if used_aad == TriPolyAADUsed::None {
39823985
return Err(DecodeError::InvalidValue);
39833986
}
39843987

@@ -4041,6 +4044,7 @@ impl<NS: NodeSigner> ReadableArgs<(Option<PublicKey>, NS)> for InboundTrampoline
40414044
fn read<R: Read>(r: &mut R, args: (Option<PublicKey>, NS)) -> Result<Self, DecodeError> {
40424045
let (update_add_blinding_point, node_signer) = args;
40434046
let receive_auth_key = node_signer.get_receive_auth_key();
4047+
let phantom_auth_key = node_signer.get_expanded_key().phantom_node_blinded_path_key;
40444048

40454049
let mut amt = None;
40464050
let mut cltv_value = None;
@@ -4094,8 +4098,9 @@ impl<NS: NodeSigner> ReadableArgs<(Option<PublicKey>, NS)> for InboundTrampoline
40944098
let rho = onion_utils::gen_rho_from_shared_secret(&enc_tlvs_ss.secret_bytes());
40954099
let mut s = Cursor::new(&enc_tlvs);
40964100
let mut reader = FixedLengthReader::new(&mut s, enc_tlvs.len() as u64);
4097-
match ChaChaDualPolyReadAdapter::read(&mut reader, (rho, receive_auth_key.0))? {
4098-
ChaChaDualPolyReadAdapter {
4101+
let read_args = (rho, receive_auth_key.0, phantom_auth_key);
4102+
match ChaChaTriPolyReadAdapter::read(&mut reader, read_args)? {
4103+
ChaChaTriPolyReadAdapter {
40994104
readable:
41004105
BlindedTrampolineTlvs::Forward(TrampolineForwardTlvs {
41014106
next_trampoline,
@@ -4110,7 +4115,7 @@ impl<NS: NodeSigner> ReadableArgs<(Option<PublicKey>, NS)> for InboundTrampoline
41104115
|| cltv_value.is_some() || total_msat.is_some()
41114116
|| keysend_preimage.is_some()
41124117
|| invoice_request.is_some()
4113-
|| used_aad
4118+
|| used_aad != TriPolyAADUsed::None
41144119
{
41154120
return Err(DecodeError::InvalidValue);
41164121
}
@@ -4123,11 +4128,11 @@ impl<NS: NodeSigner> ReadableArgs<(Option<PublicKey>, NS)> for InboundTrampoline
41234128
next_blinding_override,
41244129
}))
41254130
},
4126-
ChaChaDualPolyReadAdapter {
4131+
ChaChaTriPolyReadAdapter {
41274132
readable: BlindedTrampolineTlvs::Receive(receive_tlvs),
41284133
used_aad,
41294134
} => {
4130-
if !used_aad {
4135+
if used_aad == TriPolyAADUsed::None {
41314136
return Err(DecodeError::InvalidValue);
41324137
}
41334138

0 commit comments

Comments
 (0)