use crate::blinded_path::payment::{PaymentConstraints, PaymentRelay};
use crate::chain::channelmonitor::{HTLC_FAIL_BACK_BUFFER, LATENCY_GRACE_PERIOD_BLOCKS};
use crate::ln::PaymentHash;
-use crate::ln::channelmanager::{BlindedForward, CLTV_FAR_FAR_AWAY, HTLCFailureMsg, MIN_CLTV_EXPIRY_DELTA, PendingHTLCInfo, PendingHTLCRouting};
+use crate::ln::channelmanager::{BlindedFailure, BlindedForward, CLTV_FAR_FAR_AWAY, HTLCFailureMsg, MIN_CLTV_EXPIRY_DELTA, PendingHTLCInfo, PendingHTLCRouting};
use crate::ln::features::BlindedHopFeatures;
use crate::ln::msgs;
use crate::ln::onion_utils;
use crate::sign::{NodeSigner, Recipient};
use crate::util::logger::Logger;
+#[allow(unused_imports)]
use crate::prelude::*;
+
use core::ops::Deref;
/// Invalid inbound onion payment.
-pub struct InboundOnionErr {
+#[derive(Debug)]
+pub struct InboundHTLCErr {
/// BOLT 4 error code.
pub err_code: u16,
/// Data attached to this error.
msg: &msgs::UpdateAddHTLC, hop_data: msgs::InboundOnionPayload, hop_hmac: [u8; 32],
new_packet_bytes: [u8; onion_utils::ONION_DATA_LEN], shared_secret: [u8; 32],
next_packet_pubkey_opt: Option<Result<PublicKey, secp256k1::Error>>
-) -> Result<PendingHTLCInfo, InboundOnionErr> {
+) -> Result<PendingHTLCInfo, InboundHTLCErr> {
debug_assert!(next_packet_pubkey_opt.is_some());
let outgoing_packet = msgs::OnionPacket {
version: 0,
};
let (
- short_channel_id, amt_to_forward, outgoing_cltv_value, inbound_blinding_point
+ short_channel_id, amt_to_forward, outgoing_cltv_value, intro_node_blinding_point
) = match hop_data {
msgs::InboundOnionPayload::Forward { short_channel_id, amt_to_forward, outgoing_cltv_value } =>
(short_channel_id, amt_to_forward, outgoing_cltv_value, None),
).map_err(|()| {
// We should be returning malformed here if `msg.blinding_point` is set, but this is
// unreachable right now since we checked it in `decode_update_add_htlc_onion`.
- InboundOnionErr {
+ InboundHTLCErr {
msg: "Underflow calculating outbound amount or cltv value for blinded forward",
err_code: INVALID_ONION_BLINDING,
err_data: vec![0; 32],
}
})?;
- (short_channel_id, amt_to_forward, outgoing_cltv_value, Some(intro_node_blinding_point))
+ (short_channel_id, amt_to_forward, outgoing_cltv_value, intro_node_blinding_point)
},
msgs::InboundOnionPayload::Receive { .. } | msgs::InboundOnionPayload::BlindedReceive { .. } =>
- return Err(InboundOnionErr {
+ return Err(InboundHTLCErr {
msg: "Final Node OnionHopData provided for us as an intermediary node",
err_code: 0x4000 | 22,
err_data: Vec::new(),
routing: PendingHTLCRouting::Forward {
onion_packet: outgoing_packet,
short_channel_id,
- blinded: inbound_blinding_point.map(|bp| BlindedForward { inbound_blinding_point: bp }),
+ blinded: intro_node_blinding_point.or(msg.blinding_point)
+ .map(|bp| BlindedForward {
+ inbound_blinding_point: bp,
+ failure: intro_node_blinding_point
+ .map(|_| BlindedFailure::FromIntroductionNode)
+ .unwrap_or(BlindedFailure::FromBlindedNode),
+ }),
},
payment_hash: msg.payment_hash,
incoming_shared_secret: shared_secret,
hop_data: msgs::InboundOnionPayload, shared_secret: [u8; 32], payment_hash: PaymentHash,
amt_msat: u64, cltv_expiry: u32, phantom_shared_secret: Option<[u8; 32]>, allow_underpay: bool,
counterparty_skimmed_fee_msat: Option<u64>, current_height: u32, accept_mpp_keysend: bool,
-) -> Result<PendingHTLCInfo, InboundOnionErr> {
+) -> Result<PendingHTLCInfo, InboundHTLCErr> {
let (
- payment_data, keysend_preimage, custom_tlvs, onion_amt_msat, outgoing_cltv_value,
- payment_metadata, requires_blinded_error
+ payment_data, keysend_preimage, custom_tlvs, onion_amt_msat, onion_cltv_expiry,
+ payment_metadata, payment_context, requires_blinded_error
) = match hop_data {
msgs::InboundOnionPayload::Receive {
- payment_data, keysend_preimage, custom_tlvs, amt_msat, outgoing_cltv_value, payment_metadata, ..
+ payment_data, keysend_preimage, custom_tlvs, sender_intended_htlc_amt_msat,
+ cltv_expiry_height, payment_metadata, ..
} =>
- (payment_data, keysend_preimage, custom_tlvs, amt_msat, outgoing_cltv_value, payment_metadata,
- false),
+ (payment_data, keysend_preimage, custom_tlvs, sender_intended_htlc_amt_msat,
+ cltv_expiry_height, payment_metadata, None, false),
msgs::InboundOnionPayload::BlindedReceive {
- amt_msat, total_msat, outgoing_cltv_value, payment_secret, intro_node_blinding_point,
- payment_constraints, ..
+ sender_intended_htlc_amt_msat, total_msat, cltv_expiry_height, payment_secret,
+ intro_node_blinding_point, payment_constraints, payment_context, keysend_preimage,
+ custom_tlvs
} => {
- check_blinded_payment_constraints(amt_msat, cltv_expiry, &payment_constraints)
+ check_blinded_payment_constraints(
+ sender_intended_htlc_amt_msat, cltv_expiry, &payment_constraints
+ )
.map_err(|()| {
- InboundOnionErr {
+ InboundHTLCErr {
err_code: INVALID_ONION_BLINDING,
err_data: vec![0; 32],
msg: "Amount or cltv_expiry violated blinded payment constraints",
}
})?;
let payment_data = msgs::FinalOnionHopData { payment_secret, total_msat };
- (Some(payment_data), None, Vec::new(), amt_msat, outgoing_cltv_value, None,
+ (Some(payment_data), keysend_preimage, custom_tlvs,
+ sender_intended_htlc_amt_msat, cltv_expiry_height, None, Some(payment_context),
intro_node_blinding_point.is_none())
}
msgs::InboundOnionPayload::Forward { .. } => {
- return Err(InboundOnionErr {
+ return Err(InboundHTLCErr {
err_code: 0x4000|22,
err_data: Vec::new(),
msg: "Got non final data with an HMAC of 0",
})
},
msgs::InboundOnionPayload::BlindedForward { .. } => {
- return Err(InboundOnionErr {
+ return Err(InboundHTLCErr {
err_code: INVALID_ONION_BLINDING,
err_data: vec![0; 32],
msg: "Got blinded non final data with an HMAC of 0",
}
};
// final_incorrect_cltv_expiry
- if outgoing_cltv_value > cltv_expiry {
- return Err(InboundOnionErr {
+ if onion_cltv_expiry > cltv_expiry {
+ return Err(InboundHTLCErr {
msg: "Upstream node set CLTV to less than the CLTV set by the sender",
err_code: 18,
err_data: cltv_expiry.to_be_bytes().to_vec()
let mut err_data = Vec::with_capacity(12);
err_data.extend_from_slice(&amt_msat.to_be_bytes());
err_data.extend_from_slice(¤t_height.to_be_bytes());
- return Err(InboundOnionErr {
+ return Err(InboundHTLCErr {
err_code: 0x4000 | 15, err_data,
msg: "The final CLTV expiry is too soon to handle",
});
(allow_underpay && onion_amt_msat >
amt_msat.saturating_add(counterparty_skimmed_fee_msat.unwrap_or(0)))
{
- return Err(InboundOnionErr {
+ return Err(InboundHTLCErr {
err_code: 19,
err_data: amt_msat.to_be_bytes().to_vec(),
msg: "Upstream node sent less than we were supposed to receive in payment",
// time discrepancies due to a hash collision with X.
let hashed_preimage = PaymentHash(Sha256::hash(&payment_preimage.0).to_byte_array());
if hashed_preimage != payment_hash {
- return Err(InboundOnionErr {
+ return Err(InboundHTLCErr {
err_code: 0x4000|22,
err_data: Vec::new(),
msg: "Payment preimage didn't match payment hash",
});
}
if !accept_mpp_keysend && payment_data.is_some() {
- return Err(InboundOnionErr {
+ return Err(InboundHTLCErr {
err_code: 0x4000|22,
err_data: Vec::new(),
msg: "We don't support MPP keysend payments",
payment_data,
payment_preimage,
payment_metadata,
- incoming_cltv_expiry: outgoing_cltv_value,
+ incoming_cltv_expiry: onion_cltv_expiry,
custom_tlvs,
+ requires_blinded_error,
}
} else if let Some(data) = payment_data {
PendingHTLCRouting::Receive {
payment_data: data,
payment_metadata,
- incoming_cltv_expiry: outgoing_cltv_value,
+ payment_context,
+ incoming_cltv_expiry: onion_cltv_expiry,
phantom_shared_secret,
custom_tlvs,
requires_blinded_error,
}
} else {
- return Err(InboundOnionErr {
+ return Err(InboundHTLCErr {
err_code: 0x4000|0x2000|3,
err_data: Vec::new(),
msg: "We require payment_secrets",
incoming_shared_secret: shared_secret,
incoming_amt_msat: Some(amt_msat),
outgoing_amt_msat: onion_amt_msat,
- outgoing_cltv_value,
+ outgoing_cltv_value: onion_cltv_expiry,
skimmed_fee_msat: counterparty_skimmed_fee_msat,
})
}
pub fn peel_payment_onion<NS: Deref, L: Deref, T: secp256k1::Verification>(
msg: &msgs::UpdateAddHTLC, node_signer: &NS, logger: &L, secp_ctx: &Secp256k1<T>,
cur_height: u32, accept_mpp_keysend: bool, allow_skimmed_fees: bool,
-) -> Result<PendingHTLCInfo, InboundOnionErr>
+) -> Result<PendingHTLCInfo, InboundHTLCErr>
where
NS::Target: NodeSigner,
L::Target: Logger,
HTLCFailureMsg::Relay(r) => (0x4000 | 22, r.reason.data),
};
let msg = "Failed to decode update add htlc onion";
- InboundOnionErr { msg, err_code, err_data }
+ InboundHTLCErr { msg, err_code, err_data }
})?;
Ok(match hop {
onion_utils::Hop::Forward { next_hop_data, next_hop_hmac, new_packet_bytes } => {
} = match next_packet_details_opt {
Some(next_packet_details) => next_packet_details,
// Forward should always include the next hop details
- None => return Err(InboundOnionErr {
+ None => return Err(InboundHTLCErr {
msg: "Failed to decode update add htlc onion",
err_code: 0x4000 | 22,
err_data: Vec::new(),
if let Err((err_msg, code)) = check_incoming_htlc_cltv(
cur_height, outgoing_cltv_value, msg.cltv_expiry
) {
- return Err(InboundOnionErr {
+ return Err(InboundHTLCErr {
msg: err_msg,
err_code: code,
err_data: Vec::new(),
mod tests {
use bitcoin::hashes::Hash;
use bitcoin::hashes::sha256::Hash as Sha256;
- use bitcoin::secp256k1::{PublicKey, SecretKey};
+ use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
use crate::ln::{PaymentPreimage, PaymentHash, PaymentSecret};
use crate::ln::ChannelId;
use crate::ln::channelmanager::RecipientOnionFields;
use crate::routing::router::{Path, RouteHop};
use crate::util::test_utils;
+ #[test]
+ fn fail_construct_onion_on_too_big_payloads() {
+ // Ensure that if we call `construct_onion_packet` and friends where payloads are too large for
+ // the allotted packet length, we'll fail to construct. Previously, senders would happily
+ // construct invalid packets by array-shifting the final node's HMAC out of the packet when
+ // adding an intermediate onion layer, causing the receiver to error with "final payload
+ // provided for us as an intermediate node."
+ let secp_ctx = Secp256k1::new();
+ let bob = crate::sign::KeysManager::new(&[2; 32], 42, 42);
+ let bob_pk = PublicKey::from_secret_key(&secp_ctx, &bob.get_node_secret_key());
+ let charlie = crate::sign::KeysManager::new(&[3; 32], 42, 42);
+ let charlie_pk = PublicKey::from_secret_key(&secp_ctx, &charlie.get_node_secret_key());
+
+ let (
+ session_priv, total_amt_msat, cur_height, mut recipient_onion, keysend_preimage, payment_hash,
+ prng_seed, hops, ..
+ ) = payment_onion_args(bob_pk, charlie_pk);
+
+ // Ensure the onion will not fit all the payloads by adding a large custom TLV.
+ recipient_onion.custom_tlvs.push((13377331, vec![0; 1156]));
+
+ let path = Path { hops, blinded_tail: None, };
+ let onion_keys = super::onion_utils::construct_onion_keys(&secp_ctx, &path, &session_priv).unwrap();
+ let (onion_payloads, ..) = super::onion_utils::build_onion_payloads(
+ &path, total_amt_msat, recipient_onion, cur_height + 1, &Some(keysend_preimage)
+ ).unwrap();
+
+ assert!(super::onion_utils::construct_onion_packet(
+ onion_payloads, onion_keys, prng_seed, &payment_hash
+ ).is_err());
+ }
+
#[test]
fn test_peel_payment_onion() {
use super::*;