Skip to content

Commit 8c43f49

Browse files
Receive payment onions as new InboundPayload instead of OnionHopData
To support route blinding, we want to split OnionHopData into two separate structs, one for inbound onions and one for outbound onions. This is because blinded payloads change the fields present in the onion hop data struct based on whether we're sending vs receiving (outbound onions include encrypted blobs, inbound onions can decrypt those blobs and contain the decrypted fields themselves). In upcoming commits, we'll add variants for blinded payloads to the new InboundPayload enum. Also gets rid of some indentation in construct_recv_pending_htlc_info
1 parent c27fc7d commit 8c43f49

File tree

4 files changed

+152
-106
lines changed

4 files changed

+152
-106
lines changed

fuzz/src/msg_targets/msg_onion_hop_data.rs

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,44 @@
1313
use crate::msg_targets::utils::VecWriter;
1414
use crate::utils::test_logger;
1515

16+
macro_rules! test_onion_payload_ser_roundtrip {
17+
($data: ident) => {
18+
{
19+
use lightning::util::ser::{Writeable, Readable};
20+
let mut r = ::std::io::Cursor::new($data);
21+
if let Ok(inbound_payload_1) = <lightning::ln::msgs::InboundPayload as Readable>::read(&mut r) {
22+
let outbound_payload_1 = match inbound_payload_1 {
23+
lightning::ln::msgs::InboundPayload::Forward { short_channel_id, amt_to_forward, outgoing_cltv_value } =>
24+
lightning::ln::msgs::OnionHopData { format: lightning::ln::msgs::OnionHopDataFormat::NonFinalNode { short_channel_id }, amt_to_forward, outgoing_cltv_value },
25+
lightning::ln::msgs::InboundPayload::Receive { payment_data, keysend_preimage, amt_msat, outgoing_cltv_value } =>
26+
lightning::ln::msgs::OnionHopData { format: lightning::ln::msgs::OnionHopDataFormat::FinalNode { payment_data, keysend_preimage }, amt_to_forward: amt_msat, outgoing_cltv_value },
27+
};
28+
let mut w = VecWriter(Vec::new());
29+
outbound_payload_1.write(&mut w).unwrap();
30+
assert_eq!(outbound_payload_1.serialized_length(), w.0.len());
31+
32+
let inbound_payload_2 = <lightning::ln::msgs::InboundPayload as Readable>::read(&mut ::std::io::Cursor::new(&w.0)).unwrap();
33+
let outbound_payload_2 = match inbound_payload_2 {
34+
lightning::ln::msgs::InboundPayload::Forward { short_channel_id, amt_to_forward, outgoing_cltv_value } =>
35+
lightning::ln::msgs::OnionHopData { format: lightning::ln::msgs::OnionHopDataFormat::NonFinalNode { short_channel_id }, amt_to_forward, outgoing_cltv_value },
36+
lightning::ln::msgs::InboundPayload::Receive { payment_data, keysend_preimage, amt_msat, outgoing_cltv_value } =>
37+
lightning::ln::msgs::OnionHopData { format: lightning::ln::msgs::OnionHopDataFormat::FinalNode { payment_data, keysend_preimage }, amt_to_forward: amt_msat, outgoing_cltv_value },
38+
};
39+
let mut w_two = VecWriter(Vec::new());
40+
outbound_payload_2 .write(&mut w_two).unwrap();
41+
assert_eq!(&w.0[..], &w_two.0[..]);
42+
}
43+
}
44+
}
45+
}
46+
1647
#[inline]
1748
pub fn msg_onion_hop_data_test<Out: test_logger::Output>(data: &[u8], _out: Out) {
18-
test_msg_simple!(lightning::ln::msgs::OnionHopData, data);
49+
test_onion_payload_ser_roundtrip!(data);
1950
}
2051

2152
#[no_mangle]
2253
pub extern "C" fn msg_onion_hop_data_run(data: *const u8, datalen: usize) {
2354
let data = unsafe { std::slice::from_raw_parts(data, datalen) };
24-
test_msg_simple!(lightning::ln::msgs::OnionHopData, data);
55+
test_onion_payload_ser_roundtrip!(data);
2556
}

lightning/src/ln/channelmanager.rs

Lines changed: 63 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -2087,46 +2087,57 @@ where
20872087
}
20882088

20892089
fn construct_fwd_pending_htlc_info(
2090-
&self, msg: &msgs::UpdateAddHTLC, hop_data: msgs::OnionHopData, hop_hmac: [u8; 32],
2090+
&self, msg: &msgs::UpdateAddHTLC, hop_data: msgs::InboundPayload, hop_hmac: [u8; 32],
20912091
new_packet_bytes: [u8; onion_utils::ONION_DATA_LEN], shared_secret: [u8; 32],
20922092
) -> Result<PendingHTLCInfo, InboundOnionErr> {
20932093
let new_pubkey = msg.onion_routing_packet.public_key.unwrap();
20942094
let outgoing_packet = msgs::OnionPacket {
20952095
version: 0,
20962096
public_key: onion_utils::next_hop_packet_pubkey(&self.secp_ctx, new_pubkey, &shared_secret),
20972097
hop_data: new_packet_bytes,
2098-
hmac: hop_hmac.clone(),
2098+
hmac: hop_hmac,
20992099
};
21002100

2101-
let short_channel_id = match hop_data.format {
2102-
msgs::OnionHopDataFormat::NonFinalNode { short_channel_id } => short_channel_id,
2103-
msgs::OnionHopDataFormat::FinalNode { .. } => {
2101+
let (short_channel_id, amt_to_forward, outgoing_cltv_value) = match hop_data {
2102+
msgs::InboundPayload::Forward { short_channel_id, amt_to_forward, outgoing_cltv_value } =>
2103+
(short_channel_id, amt_to_forward, outgoing_cltv_value),
2104+
msgs::InboundPayload::Receive { .. } =>
21042105
return Err(InboundOnionErr {
21052106
msg: "Final Node OnionHopData provided for us as an intermediary node",
21062107
err_code: 0x4000 | 22,
21072108
err_data: Vec::new(),
2108-
})
2109-
},
2109+
}),
21102110
};
21112111

21122112
Ok(PendingHTLCInfo {
21132113
routing: PendingHTLCRouting::Forward {
21142114
onion_packet: outgoing_packet,
21152115
short_channel_id,
21162116
},
2117-
payment_hash: msg.payment_hash.clone(),
2117+
payment_hash: msg.payment_hash,
21182118
incoming_shared_secret: shared_secret,
21192119
incoming_amt_msat: Some(msg.amount_msat),
2120-
outgoing_amt_msat: hop_data.amt_to_forward,
2121-
outgoing_cltv_value: hop_data.outgoing_cltv_value,
2120+
outgoing_amt_msat: amt_to_forward,
2121+
outgoing_cltv_value,
21222122
})
21232123
}
21242124

2125-
fn construct_recv_pending_htlc_info(&self, hop_data: msgs::OnionHopData, shared_secret: [u8; 32],
2126-
payment_hash: PaymentHash, amt_msat: u64, cltv_expiry: u32, phantom_shared_secret: Option<[u8; 32]>) -> Result<PendingHTLCInfo, InboundOnionErr>
2125+
fn construct_recv_pending_htlc_info(&self, hop_data: msgs::InboundPayload, shared_secret: [u8; 32],
2126+
payment_hash: PaymentHash, amount_msat: u64, cltv_expiry: u32, phantom_shared_secret: Option<[u8; 32]>) -> Result<PendingHTLCInfo, InboundOnionErr>
21272127
{
2128+
let (payment_data, keysend_preimage, amt_msat, outgoing_cltv_value) = match hop_data {
2129+
msgs::InboundPayload::Receive { payment_data, keysend_preimage, amt_msat, outgoing_cltv_value, .. } =>
2130+
(payment_data, keysend_preimage, amt_msat, outgoing_cltv_value),
2131+
_ =>
2132+
return Err(InboundOnionErr {
2133+
err_code: 0x4000|22,
2134+
err_data: Vec::new(),
2135+
msg: "Got non final data with an HMAC of 0",
2136+
}),
2137+
};
2138+
21282139
// final_incorrect_cltv_expiry
2129-
if hop_data.outgoing_cltv_value != cltv_expiry {
2140+
if outgoing_cltv_value != cltv_expiry {
21302141
return Err(InboundOnionErr {
21312142
msg: "Upstream node set CLTV to the wrong value",
21322143
err_code: 18,
@@ -2141,7 +2152,7 @@ where
21412152
// payment logic has enough time to fail the HTLC backward before our onchain logic triggers a
21422153
// channel closure (see HTLC_FAIL_BACK_BUFFER rationale).
21432154
let current_height: u32 = self.best_block.read().unwrap().height();
2144-
if (hop_data.outgoing_cltv_value as u64) <= current_height as u64 + HTLC_FAIL_BACK_BUFFER as u64 + 1 {
2155+
if (outgoing_cltv_value as u64) <= current_height as u64 + HTLC_FAIL_BACK_BUFFER as u64 + 1 {
21452156
let mut err_data = Vec::with_capacity(12);
21462157
err_data.extend_from_slice(&amt_msat.to_be_bytes());
21472158
err_data.extend_from_slice(&current_height.to_be_bytes());
@@ -2150,70 +2161,60 @@ where
21502161
msg: "The final CLTV expiry is too soon to handle",
21512162
});
21522163
}
2153-
if hop_data.amt_to_forward > amt_msat {
2164+
if amt_msat > amount_msat {
21542165
return Err(InboundOnionErr {
21552166
err_code: 19,
21562167
err_data: amt_msat.to_be_bytes().to_vec(),
21572168
msg: "Upstream node sent less than we were supposed to receive in payment",
21582169
});
21592170
}
21602171

2161-
let routing = match hop_data.format {
2162-
msgs::OnionHopDataFormat::NonFinalNode { .. } => {
2172+
let routing;
2173+
if payment_data.is_some() && keysend_preimage.is_some() {
2174+
return Err(InboundOnionErr {
2175+
err_code: 0x4000|22,
2176+
err_data: Vec::new(),
2177+
msg: "We don't support MPP keysend payments",
2178+
});
2179+
} else if let Some(data) = payment_data {
2180+
routing = PendingHTLCRouting::Receive {
2181+
payment_data: data,
2182+
incoming_cltv_expiry: outgoing_cltv_value,
2183+
phantom_shared_secret,
2184+
}
2185+
} else if let Some(payment_preimage) = keysend_preimage {
2186+
// We need to check that the sender knows the keysend preimage before processing this
2187+
// payment further. Otherwise, an intermediary routing hop forwarding non-keysend-HTLC X
2188+
// could discover the final destination of X, by probing the adjacent nodes on the route
2189+
// with a keysend payment of identical payment hash to X and observing the processing
2190+
// time discrepancies due to a hash collision with X.
2191+
let hashed_preimage = PaymentHash(Sha256::hash(&payment_preimage.0).into_inner());
2192+
if hashed_preimage != payment_hash {
21632193
return Err(InboundOnionErr {
21642194
err_code: 0x4000|22,
21652195
err_data: Vec::new(),
2166-
msg: "Got non final data with an HMAC of 0",
2196+
msg: "Payment preimage didn't match payment hash",
21672197
});
2168-
},
2169-
msgs::OnionHopDataFormat::FinalNode { payment_data, keysend_preimage } => {
2170-
if payment_data.is_some() && keysend_preimage.is_some() {
2171-
return Err(InboundOnionErr {
2172-
err_code: 0x4000|22,
2173-
err_data: Vec::new(),
2174-
msg: "We don't support MPP keysend payments",
2175-
});
2176-
} else if let Some(data) = payment_data {
2177-
PendingHTLCRouting::Receive {
2178-
payment_data: data,
2179-
incoming_cltv_expiry: hop_data.outgoing_cltv_value,
2180-
phantom_shared_secret,
2181-
}
2182-
} else if let Some(payment_preimage) = keysend_preimage {
2183-
// We need to check that the sender knows the keysend preimage before processing this
2184-
// payment further. Otherwise, an intermediary routing hop forwarding non-keysend-HTLC X
2185-
// could discover the final destination of X, by probing the adjacent nodes on the route
2186-
// with a keysend payment of identical payment hash to X and observing the processing
2187-
// time discrepancies due to a hash collision with X.
2188-
let hashed_preimage = PaymentHash(Sha256::hash(&payment_preimage.0).into_inner());
2189-
if hashed_preimage != payment_hash {
2190-
return Err(InboundOnionErr {
2191-
err_code: 0x4000|22,
2192-
err_data: Vec::new(),
2193-
msg: "Payment preimage didn't match payment hash",
2194-
});
2195-
}
2198+
}
21962199

2197-
PendingHTLCRouting::ReceiveKeysend {
2198-
payment_preimage,
2199-
incoming_cltv_expiry: hop_data.outgoing_cltv_value,
2200-
}
2201-
} else {
2202-
return Err(InboundOnionErr {
2203-
err_code: 0x4000|0x2000|3,
2204-
err_data: Vec::new(),
2205-
msg: "We require payment_secrets",
2206-
});
2207-
}
2208-
},
2209-
};
2200+
routing = PendingHTLCRouting::ReceiveKeysend {
2201+
payment_preimage,
2202+
incoming_cltv_expiry: outgoing_cltv_value,
2203+
};
2204+
} else {
2205+
return Err(InboundOnionErr {
2206+
err_code: 0x4000|0x2000|3,
2207+
err_data: Vec::new(),
2208+
msg: "We require payment_secrets",
2209+
});
2210+
}
22102211
Ok(PendingHTLCInfo {
22112212
routing,
22122213
payment_hash,
22132214
incoming_shared_secret: shared_secret,
2214-
incoming_amt_msat: Some(amt_msat),
2215-
outgoing_amt_msat: amt_msat,
2216-
outgoing_cltv_value: hop_data.outgoing_cltv_value,
2215+
incoming_amt_msat: Some(amount_msat),
2216+
outgoing_amt_msat: amount_msat,
2217+
outgoing_cltv_value,
22172218
})
22182219
}
22192220

0 commit comments

Comments
 (0)