Skip to content

Commit

Permalink
Include HMAC and Nonce in payment::ReceiveTlvs
Browse files Browse the repository at this point in the history
In order to authenticate a PaymentContext, an HMAC and Nonce must be
included along with it in payment::ReceiveTlvs. Compute the HMAC when
constructing a BlindedPaymentPath and include it in the recipient's
BlindedPaymentTlvs. Authentication will be added in an upcoming commit.
  • Loading branch information
jkczyz committed Dec 11, 2024
1 parent 5f068df commit 5350bfa
Show file tree
Hide file tree
Showing 7 changed files with 65 additions and 11 deletions.
1 change: 1 addition & 0 deletions fuzz/src/invoice_request_deser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ fn build_response<T: secp256k1::Signing + secp256k1::Verification>(
htlc_minimum_msat: 1,
},
payment_context,
authentication: None,
};
let intermediate_nodes = [PaymentForwardNode {
tlvs: ForwardTlvs {
Expand Down
1 change: 1 addition & 0 deletions fuzz/src/refund_deser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ fn build_response<T: secp256k1::Signing + secp256k1::Verification>(
htlc_minimum_msat: 1,
},
payment_context,
authentication: None,
};
let intermediate_nodes = [PaymentForwardNode {
tlvs: ForwardTlvs {
Expand Down
15 changes: 14 additions & 1 deletion lightning/src/blinded_path/payment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

//! Data structures and methods for constructing [`BlindedPaymentPath`]s to send a payment over.
use bitcoin::hashes::hmac::Hmac;
use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey};

use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode, NodeIdLookUp};
Expand All @@ -22,6 +24,7 @@ use crate::types::features::BlindedHopFeatures;
use crate::ln::msgs::DecodeError;
use crate::ln::onion_utils;
use crate::offers::invoice_request::InvoiceRequestFields;
use crate::offers::nonce::Nonce;
use crate::offers::offer::OfferId;
use crate::routing::gossip::{NodeId, ReadOnlyNetworkGraph};
use crate::sign::{EntropySource, NodeSigner, Recipient};
Expand Down Expand Up @@ -260,6 +263,8 @@ pub struct ReceiveTlvs {
pub payment_constraints: PaymentConstraints,
/// Context for the receiver of this payment.
pub payment_context: PaymentContext,
/// An HMAC of `payment_context` along with a nonce used to construct it.
pub authentication: Option<(Hmac<Sha256>, Nonce)>,
}

/// Data to construct a [`BlindedHop`] for sending a payment over.
Expand Down Expand Up @@ -404,7 +409,8 @@ impl Writeable for ReceiveTlvs {
encode_tlv_stream!(w, {
(12, self.payment_constraints, required),
(65536, self.payment_secret, required),
(65537, self.payment_context, required)
(65537, self.payment_context, required),
(65539, self.authentication, option),
});
Ok(())
}
Expand Down Expand Up @@ -432,6 +438,7 @@ impl Readable for BlindedPaymentTlvs {
(14, features, (option, encoding: (BlindedHopFeatures, WithoutLength))),
(65536, payment_secret, option),
(65537, payment_context, (default_value, PaymentContext::unknown())),
(65539, authentication, option),
});
let _padding: Option<utils::Padding> = _padding;

Expand All @@ -452,6 +459,7 @@ impl Readable for BlindedPaymentTlvs {
payment_secret: payment_secret.ok_or(DecodeError::InvalidValue)?,
payment_constraints: payment_constraints.0.unwrap(),
payment_context: payment_context.0.unwrap(),
authentication,
}))
}
}
Expand Down Expand Up @@ -683,6 +691,7 @@ mod tests {
htlc_minimum_msat: 1,
},
payment_context: PaymentContext::unknown(),
authentication: None,
};
let htlc_maximum_msat = 100_000;
let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, htlc_maximum_msat, 12).unwrap();
Expand All @@ -702,6 +711,7 @@ mod tests {
htlc_minimum_msat: 1,
},
payment_context: PaymentContext::unknown(),
authentication: None,
};
let blinded_payinfo = super::compute_payinfo(&[], &recv_tlvs, 4242, TEST_FINAL_CLTV as u16).unwrap();
assert_eq!(blinded_payinfo.fee_base_msat, 0);
Expand Down Expand Up @@ -758,6 +768,7 @@ mod tests {
htlc_minimum_msat: 3,
},
payment_context: PaymentContext::unknown(),
authentication: None,
};
let htlc_maximum_msat = 100_000;
let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, htlc_maximum_msat, TEST_FINAL_CLTV as u16).unwrap();
Expand Down Expand Up @@ -811,6 +822,7 @@ mod tests {
htlc_minimum_msat: 1,
},
payment_context: PaymentContext::unknown(),
authentication: None,
};
let htlc_minimum_msat = 3798;
assert!(super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, htlc_minimum_msat - 1, TEST_FINAL_CLTV as u16).is_err());
Expand Down Expand Up @@ -868,6 +880,7 @@ mod tests {
htlc_minimum_msat: 1,
},
payment_context: PaymentContext::unknown(),
authentication: None,
};

let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, 10_000, TEST_FINAL_CLTV as u16).unwrap();
Expand Down
35 changes: 29 additions & 6 deletions lightning/src/ln/blinded_payment_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
// You may not use this file except in accordance with one or both of these
// licenses.

use bitcoin::hashes::hmac::Hmac;
use bitcoin::hashes::hex::FromHex;
use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey, schnorr};
use bitcoin::secp256k1::ecdh::SharedSecret;
use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature};
Expand All @@ -17,16 +19,18 @@ use crate::events::{Event, HTLCDestination, MessageSendEvent, MessageSendEventsP
use crate::ln::types::ChannelId;
use crate::types::payment::{PaymentHash, PaymentSecret};
use crate::ln::channelmanager;
use crate::ln::channelmanager::{HTLCFailureMsg, PaymentId, RecipientOnionFields};
use crate::ln::channelmanager::{HTLCFailureMsg, PaymentId, RecipientOnionFields, Verification};
use crate::types::features::{BlindedHopFeatures, ChannelFeatures, NodeFeatures};
use crate::ln::functional_test_utils::*;
use crate::ln::inbound_payment::ExpandedKey;
use crate::ln::msgs;
use crate::ln::msgs::{ChannelMessageHandler, UnsignedGossipMessage};
use crate::ln::onion_payment;
use crate::ln::onion_utils;
use crate::ln::onion_utils::INVALID_ONION_BLINDING;
use crate::ln::outbound_payment::{Retry, IDEMPOTENCY_TIMEOUT_TICKS};
use crate::offers::invoice::UnsignedBolt12Invoice;
use crate::offers::nonce::Nonce;
use crate::prelude::*;
use crate::routing::router::{BlindedTail, Path, Payee, PaymentParameters, RouteHop, RouteParameters};
use crate::sign::{KeyMaterial, NodeSigner, Recipient};
Expand Down Expand Up @@ -69,15 +73,19 @@ fn blinded_payment_path(
.unwrap_or_else(|| channel_upds[idx - 1].htlc_maximum_msat),
});
}
let payee_tlvs = ReceiveTlvs {

let mut payee_tlvs = ReceiveTlvs {
payment_secret,
payment_constraints: PaymentConstraints {
max_cltv_expiry: u32::max_value(),
htlc_minimum_msat:
intro_node_min_htlc_opt.unwrap_or_else(|| channel_upds.last().unwrap().htlc_minimum_msat),
},
payment_context: PaymentContext::unknown(),
authentication: None,
};
payee_tlvs.authentication = Some(hmac_payee_tlvs(&payee_tlvs, keys_manager));

let mut secp_ctx = Secp256k1::new();
BlindedPaymentPath::new(
&intermediate_nodes[..], *node_ids.last().unwrap(), payee_tlvs,
Expand All @@ -86,6 +94,15 @@ fn blinded_payment_path(
).unwrap()
}

fn hmac_payee_tlvs(
payee_tlvs: &ReceiveTlvs, keys_manager: &test_utils::TestKeysInterface,
) -> (Hmac<Sha256>, Nonce) {
let nonce = Nonce([42u8; 16]);
let expanded_key = ExpandedKey::new(&keys_manager.get_inbound_payment_key_material());
let hmac = payee_tlvs.hmac_for_offer_payment(nonce, &expanded_key);
(hmac, nonce)
}

pub fn get_blinded_route_parameters(
amt_msat: u64, payment_secret: PaymentSecret, intro_node_min_htlc: u64, intro_node_max_htlc: u64,
node_ids: Vec<PublicKey>, channel_upds: &[&msgs::UnsignedChannelUpdate],
Expand Down Expand Up @@ -116,14 +133,16 @@ fn do_one_hop_blinded_path(success: bool) {

let amt_msat = 5000;
let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[1], Some(amt_msat), None);
let payee_tlvs = ReceiveTlvs {
let mut payee_tlvs = ReceiveTlvs {
payment_secret,
payment_constraints: PaymentConstraints {
max_cltv_expiry: u32::max_value(),
htlc_minimum_msat: chan_upd.htlc_minimum_msat,
},
payment_context: PaymentContext::unknown(),
authentication: None,
};
payee_tlvs.authentication = Some(hmac_payee_tlvs(&payee_tlvs, &chanmon_cfgs[1].keys_manager));
let mut secp_ctx = Secp256k1::new();
let blinded_path = BlindedPaymentPath::new(
&[], nodes[1].node.get_our_node_id(), payee_tlvs, u64::MAX, TEST_FINAL_CLTV as u16,
Expand Down Expand Up @@ -160,14 +179,16 @@ fn mpp_to_one_hop_blinded_path() {

let amt_msat = 15_000_000;
let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[3], Some(amt_msat), None);
let payee_tlvs = ReceiveTlvs {
let mut payee_tlvs = ReceiveTlvs {
payment_secret,
payment_constraints: PaymentConstraints {
max_cltv_expiry: u32::max_value(),
htlc_minimum_msat: chan_upd_1_3.htlc_minimum_msat,
},
payment_context: PaymentContext::unknown(),
authentication: None,
};
payee_tlvs.authentication = Some(hmac_payee_tlvs(&payee_tlvs, &chanmon_cfgs[3].keys_manager));
let blinded_path = BlindedPaymentPath::new(
&[], nodes[3].node.get_our_node_id(), payee_tlvs, u64::MAX, TEST_FINAL_CLTV as u16,
&chanmon_cfgs[3].keys_manager, &secp_ctx
Expand Down Expand Up @@ -302,7 +323,7 @@ fn do_forward_checks_failure(check: ForwardCheckFail, intro_fails: bool) {
let mut route_params = get_blinded_route_parameters(amt_msat, payment_secret, 1, 1_0000_0000,
nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(),
&[&chan_upd_1_2, &chan_upd_2_3], &chanmon_cfgs[3].keys_manager);
route_params.payment_params.max_path_length = 18;
route_params.payment_params.max_path_length = 17;

let route = get_route(&nodes[0], &route_params).unwrap();
node_cfgs[0].router.expect_find_route(route_params.clone(), Ok(route.clone()));
Expand Down Expand Up @@ -1375,14 +1396,16 @@ fn custom_tlvs_to_blinded_path() {

let amt_msat = 5000;
let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[1], Some(amt_msat), None);
let payee_tlvs = ReceiveTlvs {
let mut payee_tlvs = ReceiveTlvs {
payment_secret,
payment_constraints: PaymentConstraints {
max_cltv_expiry: u32::max_value(),
htlc_minimum_msat: chan_upd.htlc_minimum_msat,
},
payment_context: PaymentContext::unknown(),
authentication: None,
};
payee_tlvs.authentication = Some(hmac_payee_tlvs(&payee_tlvs, &chanmon_cfgs[1].keys_manager));
let mut secp_ctx = Secp256k1::new();
let blinded_path = BlindedPaymentPath::new(
&[], nodes[1].node.get_our_node_id(), payee_tlvs, u64::MAX, TEST_FINAL_CLTV as u16,
Expand Down
10 changes: 9 additions & 1 deletion lightning/src/ln/channelmanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10472,20 +10472,28 @@ where
fn create_blinded_payment_paths(
&self, amount_msats: u64, payment_secret: PaymentSecret, payment_context: PaymentContext
) -> Result<Vec<BlindedPaymentPath>, ()> {
let expanded_key = &self.inbound_payment_key;
let entropy = &*self.entropy_source;
let secp_ctx = &self.secp_ctx;

let first_hops = self.list_usable_channels();
let payee_node_id = self.get_our_node_id();
let max_cltv_expiry = self.best_block.read().unwrap().height + CLTV_FAR_FAR_AWAY
+ LATENCY_GRACE_PERIOD_BLOCKS;
let payee_tlvs = ReceiveTlvs {

let mut payee_tlvs = ReceiveTlvs {
payment_secret,
payment_constraints: PaymentConstraints {
max_cltv_expiry,
htlc_minimum_msat: 1,
},
payment_context,
authentication: None,
};
let nonce = Nonce::from_entropy_source(entropy);
let hmac = payee_tlvs.hmac_for_offer_payment(nonce, expanded_key);
payee_tlvs.authentication = Some((hmac, nonce));

self.router.create_blinded_payment_paths(
payee_node_id, first_hops, payee_tlvs, amount_msats, secp_ctx
)
Expand Down
12 changes: 10 additions & 2 deletions lightning/src/ln/max_payment_path_len_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,19 @@ use crate::blinded_path::payment::{BlindedPayInfo, BlindedPaymentPath, PaymentCo
use crate::events::{Event, MessageSendEventsProvider};
use crate::types::payment::PaymentSecret;
use crate::ln::blinded_payment_tests::get_blinded_route_parameters;
use crate::ln::channelmanager::PaymentId;
use crate::ln::channelmanager::{PaymentId, Verification};
use crate::types::features::BlindedHopFeatures;
use crate::ln::functional_test_utils::*;
use crate::ln::inbound_payment::ExpandedKey;
use crate::ln::msgs;
use crate::ln::msgs::OnionMessageHandler;
use crate::ln::onion_utils;
use crate::ln::onion_utils::MIN_FINAL_VALUE_ESTIMATE_WITH_OVERPAY;
use crate::ln::outbound_payment::{RecipientOnionFields, Retry, RetryableSendFailure};
use crate::offers::nonce::Nonce;
use crate::prelude::*;
use crate::routing::router::{DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA, PaymentParameters, RouteParameters};
use crate::sign::NodeSigner;
use crate::util::errors::APIError;
use crate::util::ser::Writeable;
use crate::util::test_utils;
Expand Down Expand Up @@ -157,14 +160,19 @@ fn one_hop_blinded_path_with_custom_tlv() {
// Construct the route parameters for sending to nodes[2]'s 1-hop blinded path.
let amt_msat = 100_000;
let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None);
let payee_tlvs = ReceiveTlvs {
let mut payee_tlvs = ReceiveTlvs {
payment_secret,
payment_constraints: PaymentConstraints {
max_cltv_expiry: u32::max_value(),
htlc_minimum_msat: chan_upd_1_2.htlc_minimum_msat,
},
payment_context: PaymentContext::unknown(),
authentication: None,
};
let nonce = Nonce([42u8; 16]);
let expanded_key = ExpandedKey::new(&chanmon_cfgs[2].keys_manager.get_inbound_payment_key_material());
let hmac = payee_tlvs.hmac_for_offer_payment(nonce, &expanded_key);
payee_tlvs.authentication = Some((hmac, nonce));
let mut secp_ctx = Secp256k1::new();
let blinded_path = BlindedPaymentPath::new(
&[], nodes[2].node.get_our_node_id(), payee_tlvs, u64::MAX, TEST_FINAL_CLTV as u16,
Expand Down
2 changes: 1 addition & 1 deletion lightning/src/ln/msgs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2908,7 +2908,7 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, NS)> for InboundOnionPayload wh
})
},
ChaChaPolyReadAdapter { readable: BlindedPaymentTlvs::Receive(ReceiveTlvs {
payment_secret, payment_constraints, payment_context
payment_secret, payment_constraints, payment_context, authentication: _,
})} => {
if total_msat.unwrap_or(0) > MAX_VALUE_MSAT { return Err(DecodeError::InvalidValue) }
Ok(Self::BlindedReceive {
Expand Down

0 comments on commit 5350bfa

Please sign in to comment.