//! Primarily features [`peel_payment_onion`], which allows the decoding of an onion statelessly
//! and can be used to predict whether we'd accept a payment.
-use bitcoin::hashes::Hash;
+use bitcoin::hashes::{Hash, HashEngine};
+use bitcoin::hashes::hmac::{Hmac, HmacEngine};
use bitcoin::hashes::sha256::Hash as Sha256;
-use bitcoin::secp256k1::{self, Secp256k1, PublicKey};
+use bitcoin::secp256k1::{self, PublicKey, Scalar, Secp256k1};
use crate::blinded_path;
use crate::blinded_path::payment::{PaymentConstraints, PaymentRelay};
use core::ops::Deref;
/// Invalid inbound onion payment.
+#[derive(Debug)]
pub struct InboundOnionErr {
/// BOLT 4 error code.
pub err_code: u16,
pub msg: &'static str,
}
+fn check_blinded_payment_constraints(
+ amt_msat: u64, cltv_expiry: u32, constraints: &PaymentConstraints
+) -> Result<(), ()> {
+ if amt_msat < constraints.htlc_minimum_msat ||
+ cltv_expiry > constraints.max_cltv_expiry
+ { return Err(()) }
+ Ok(())
+}
+
fn check_blinded_forward(
inbound_amt_msat: u64, inbound_cltv_expiry: u32, payment_relay: &PaymentRelay,
payment_constraints: &PaymentConstraints, features: &BlindedHopFeatures
let outgoing_cltv_value = inbound_cltv_expiry.checked_sub(
payment_relay.cltv_expiry_delta as u32
).ok_or(())?;
- if inbound_amt_msat < payment_constraints.htlc_minimum_msat ||
- outgoing_cltv_value > payment_constraints.max_cltv_expiry
- { return Err(()) }
+ check_blinded_payment_constraints(inbound_amt_msat, outgoing_cltv_value, payment_constraints)?;
+
if features.requires_unknown_bits_from(&BlindedHopFeatures::empty()) { return Err(()) }
Ok((amt_to_forward, outgoing_cltv_value))
}
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> {
- let (payment_data, keysend_preimage, custom_tlvs, onion_amt_msat, outgoing_cltv_value, payment_metadata) = match hop_data {
+ let (
+ payment_data, keysend_preimage, custom_tlvs, onion_amt_msat, outgoing_cltv_value,
+ payment_metadata, 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, amt_msat, outgoing_cltv_value, payment_metadata),
+ (payment_data, keysend_preimage, custom_tlvs, amt_msat, outgoing_cltv_value, payment_metadata,
+ false),
msgs::InboundOnionPayload::BlindedReceive {
- amt_msat, total_msat, outgoing_cltv_value, payment_secret, ..
+ amt_msat, total_msat, outgoing_cltv_value, payment_secret, intro_node_blinding_point,
+ payment_constraints, ..
} => {
+ check_blinded_payment_constraints(amt_msat, cltv_expiry, &payment_constraints)
+ .map_err(|()| {
+ InboundOnionErr {
+ 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), None, Vec::new(), amt_msat, outgoing_cltv_value, None,
+ intro_node_blinding_point.is_none())
}
msgs::InboundOnionPayload::Forward { .. } => {
return Err(InboundOnionErr {
incoming_cltv_expiry: outgoing_cltv_value,
phantom_shared_secret,
custom_tlvs,
+ requires_blinded_error,
}
} else {
return Err(InboundOnionErr {
($msg: expr, $err_code: expr) => {
{
log_info!(logger, "Failed to accept/forward incoming HTLC: {}", $msg);
+ let (sha256_of_onion, failure_code) = if msg.blinding_point.is_some() {
+ ([0; 32], INVALID_ONION_BLINDING)
+ } else {
+ (Sha256::hash(&msg.onion_routing_packet.hop_data).to_byte_array(), $err_code)
+ };
return Err(HTLCFailureMsg::Malformed(msgs::UpdateFailMalformedHTLC {
channel_id: msg.channel_id,
htlc_id: msg.htlc_id,
- sha256_of_onion: Sha256::hash(&msg.onion_routing_packet.hop_data).to_byte_array(),
- failure_code: $err_code,
+ sha256_of_onion,
+ failure_code,
}));
}
}
return_malformed_err!("invalid ephemeral pubkey", 0x8000 | 0x4000 | 6);
}
+ let blinded_node_id_tweak = msg.blinding_point.map(|bp| {
+ let blinded_tlvs_ss = node_signer.ecdh(Recipient::Node, &bp, None).unwrap().secret_bytes();
+ let mut hmac = HmacEngine::<Sha256>::new(b"blinded_node_id");
+ hmac.input(blinded_tlvs_ss.as_ref());
+ Scalar::from_be_bytes(Hmac::from_engine(hmac).to_byte_array()).unwrap()
+ });
let shared_secret = node_signer.ecdh(
- Recipient::Node, &msg.onion_routing_packet.public_key.unwrap(), None
+ Recipient::Node, &msg.onion_routing_packet.public_key.unwrap(), blinded_node_id_tweak.as_ref()
).unwrap().secret_bytes();
if msg.onion_routing_packet.version != 0 {
macro_rules! return_err {
($msg: expr, $err_code: expr, $data: expr) => {
{
+ if msg.blinding_point.is_some() {
+ return_malformed_err!($msg, INVALID_ONION_BLINDING)
+ }
+
log_info!(logger, "Failed to accept/forward incoming HTLC: {}", $msg);
return Err(HTLCFailureMsg::Relay(msgs::UpdateFailHTLC {
channel_id: msg.channel_id,
let next_hop = match onion_utils::decode_next_payment_hop(
shared_secret, &msg.onion_routing_packet.hop_data[..], msg.onion_routing_packet.hmac,
- msg.payment_hash, node_signer
+ msg.payment_hash, msg.blinding_point, node_signer
) {
Ok(res) => res,
Err(onion_utils::OnionDecodeErr::Malformed { err_msg, err_code }) => {
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::*;