Skip to content

Commit 1c11e70

Browse files
committed
Make ReceiveTlvs::authentication required
Having ReceiveTlvs::authentication be an Option makes for an odd interface as ReceiveTlvs first needs to be constructed with it set to None. Afterward, the HMAC can be constructed from it and set on it as Some. Instead, introduce an UnauthenticatedReceiveTlvs struct with a method to convert to an HMAC'ed ReceiveTlvs. This prevents ReceiveTlvs from being in an invalid state.
1 parent f9f8191 commit 1c11e70

File tree

9 files changed

+121
-82
lines changed

9 files changed

+121
-82
lines changed

fuzz/src/invoice_request_deser.rs

+8-4
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@ use bitcoin::secp256k1::{self, Keypair, Parity, PublicKey, Secp256k1, SecretKey}
1212
use core::convert::TryFrom;
1313
use lightning::blinded_path::payment::{
1414
BlindedPaymentPath, Bolt12OfferContext, ForwardTlvs, PaymentConstraints, PaymentContext,
15-
PaymentForwardNode, PaymentRelay, ReceiveTlvs,
15+
PaymentForwardNode, PaymentRelay, UnauthenticatedReceiveTlvs,
1616
};
1717
use lightning::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA;
18+
use lightning::ln::inbound_payment::ExpandedKey;
1819
use lightning::offers::invoice::UnsignedBolt12Invoice;
1920
use lightning::offers::invoice_request::{InvoiceRequest, InvoiceRequestFields};
21+
use lightning::offers::nonce::Nonce;
2022
use lightning::offers::offer::OfferId;
2123
use lightning::offers::parse::Bolt12SemanticError;
2224
use lightning::sign::EntropySource;
@@ -80,7 +82,9 @@ fn privkey(byte: u8) -> SecretKey {
8082
fn build_response<T: secp256k1::Signing + secp256k1::Verification>(
8183
invoice_request: &InvoiceRequest, secp_ctx: &Secp256k1<T>,
8284
) -> Result<UnsignedBolt12Invoice, Bolt12SemanticError> {
85+
let expanded_key = ExpandedKey::new([42; 32]);
8386
let entropy_source = Randomness {};
87+
let nonce = Nonce::from_entropy_source(&entropy_source);
8488
let payment_context = PaymentContext::Bolt12Offer(Bolt12OfferContext {
8589
offer_id: OfferId([42; 32]),
8690
invoice_request: InvoiceRequestFields {
@@ -92,15 +96,15 @@ fn build_response<T: secp256k1::Signing + secp256k1::Verification>(
9296
human_readable_name: None,
9397
},
9498
});
95-
let payee_tlvs = ReceiveTlvs {
99+
let payee_tlvs = UnauthenticatedReceiveTlvs {
96100
payment_secret: PaymentSecret([42; 32]),
97101
payment_constraints: PaymentConstraints {
98102
max_cltv_expiry: 1_000_000,
99103
htlc_minimum_msat: 1,
100104
},
101105
payment_context,
102-
authentication: None,
103106
};
107+
let payee_tlvs = payee_tlvs.authenticate(nonce, &expanded_key);
104108
let intermediate_nodes = [PaymentForwardNode {
105109
tlvs: ForwardTlvs {
106110
short_channel_id: 43,
@@ -110,7 +114,7 @@ fn build_response<T: secp256k1::Signing + secp256k1::Verification>(
110114
fee_base_msat: 1,
111115
},
112116
payment_constraints: PaymentConstraints {
113-
max_cltv_expiry: payee_tlvs.payment_constraints.max_cltv_expiry + 40,
117+
max_cltv_expiry: payee_tlvs.tlvs().payment_constraints.max_cltv_expiry + 40,
114118
htlc_minimum_msat: 100,
115119
},
116120
features: BlindedHopFeatures::empty(),

fuzz/src/refund_deser.rs

+8-4
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@ use bitcoin::secp256k1::{self, Keypair, PublicKey, Secp256k1, SecretKey};
1212
use core::convert::TryFrom;
1313
use lightning::blinded_path::payment::{
1414
BlindedPaymentPath, Bolt12RefundContext, ForwardTlvs, PaymentConstraints, PaymentContext,
15-
PaymentForwardNode, PaymentRelay, ReceiveTlvs,
15+
PaymentForwardNode, PaymentRelay, UnauthenticatedReceiveTlvs,
1616
};
1717
use lightning::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA;
18+
use lightning::ln::inbound_payment::ExpandedKey;
1819
use lightning::offers::invoice::UnsignedBolt12Invoice;
20+
use lightning::offers::nonce::Nonce;
1921
use lightning::offers::parse::Bolt12SemanticError;
2022
use lightning::offers::refund::Refund;
2123
use lightning::sign::EntropySource;
@@ -67,17 +69,19 @@ fn privkey(byte: u8) -> SecretKey {
6769
fn build_response<T: secp256k1::Signing + secp256k1::Verification>(
6870
refund: &Refund, signing_pubkey: PublicKey, secp_ctx: &Secp256k1<T>,
6971
) -> Result<UnsignedBolt12Invoice, Bolt12SemanticError> {
72+
let expanded_key = ExpandedKey::new([42; 32]);
7073
let entropy_source = Randomness {};
74+
let nonce = Nonce::from_entropy_source(&entropy_source);
7175
let payment_context = PaymentContext::Bolt12Refund(Bolt12RefundContext {});
72-
let payee_tlvs = ReceiveTlvs {
76+
let payee_tlvs = UnauthenticatedReceiveTlvs {
7377
payment_secret: PaymentSecret([42; 32]),
7478
payment_constraints: PaymentConstraints {
7579
max_cltv_expiry: 1_000_000,
7680
htlc_minimum_msat: 1,
7781
},
7882
payment_context,
79-
authentication: None,
8083
};
84+
let payee_tlvs = payee_tlvs.authenticate(nonce, &expanded_key);
8185
let intermediate_nodes = [PaymentForwardNode {
8286
tlvs: ForwardTlvs {
8387
short_channel_id: 43,
@@ -87,7 +91,7 @@ fn build_response<T: secp256k1::Signing + secp256k1::Verification>(
8791
fee_base_msat: 1,
8892
},
8993
payment_constraints: PaymentConstraints {
90-
max_cltv_expiry: payee_tlvs.payment_constraints.max_cltv_expiry + 40,
94+
max_cltv_expiry: payee_tlvs.tlvs().payment_constraints.max_cltv_expiry + 40,
9195
htlc_minimum_msat: 100,
9296
},
9397
features: BlindedHopFeatures::empty(),

lightning/src/blinded_path/payment.rs

+57-20
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ use crate::io;
2020
use crate::io::Cursor;
2121
use crate::types::payment::PaymentSecret;
2222
use crate::ln::channel_state::CounterpartyForwardingInfo;
23+
use crate::ln::channelmanager::Verification;
2324
use crate::types::features::BlindedHopFeatures;
25+
use crate::ln::inbound_payment::ExpandedKey;
2426
use crate::ln::msgs::DecodeError;
2527
use crate::ln::onion_utils;
2628
use crate::offers::invoice_request::InvoiceRequestFields;
@@ -117,7 +119,7 @@ impl BlindedPaymentPath {
117119
let blinding_secret = SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted");
118120

119121
let blinded_payinfo = compute_payinfo(
120-
intermediate_nodes, &payee_tlvs, htlc_maximum_msat, min_final_cltv_expiry_delta
122+
intermediate_nodes, &payee_tlvs.tlvs, htlc_maximum_msat, min_final_cltv_expiry_delta
121123
)?;
122124
Ok(Self {
123125
inner_path: BlindedPath {
@@ -255,16 +257,43 @@ pub struct ForwardTlvs {
255257

256258
/// Data to construct a [`BlindedHop`] for receiving a payment. This payload is custom to LDK and
257259
/// may not be valid if received by another lightning implementation.
260+
///
261+
/// Can only be constructed by calling [`UnauthenticatedReceiveTlvs::authenticate`].
258262
#[derive(Clone, Debug)]
259263
pub struct ReceiveTlvs {
264+
/// The TLVs for which the HMAC in `authentication` is derived.
265+
pub(crate) tlvs: UnauthenticatedReceiveTlvs,
266+
/// An HMAC of `tlvs` along with a nonce used to construct it.
267+
pub(crate) authentication: (Hmac<Sha256>, Nonce),
268+
}
269+
270+
impl ReceiveTlvs {
271+
/// Returns the underlying TLVs.
272+
pub fn tlvs(&self) -> &UnauthenticatedReceiveTlvs {
273+
&self.tlvs
274+
}
275+
}
276+
277+
/// An unauthenticated [`ReceiveTlvs`].
278+
#[derive(Clone, Debug)]
279+
pub struct UnauthenticatedReceiveTlvs {
260280
/// Used to authenticate the sender of a payment to the receiver and tie MPP HTLCs together.
261281
pub payment_secret: PaymentSecret,
262282
/// Constraints for the receiver of this payment.
263283
pub payment_constraints: PaymentConstraints,
264284
/// Context for the receiver of this payment.
265285
pub payment_context: PaymentContext,
266-
/// An HMAC of `payment_context` along with a nonce used to construct it.
267-
pub authentication: Option<(Hmac<Sha256>, Nonce)>,
286+
}
287+
288+
impl UnauthenticatedReceiveTlvs {
289+
/// Creates an authenticated [`ReceiveTlvs`], which includes an HMAC and the provide [`Nonce`]
290+
/// that can be use later to verify it authenticity.
291+
pub fn authenticate(self, nonce: Nonce, expanded_key: &ExpandedKey) -> ReceiveTlvs {
292+
ReceiveTlvs {
293+
authentication: (self.hmac_for_offer_payment(nonce, expanded_key), nonce),
294+
tlvs: self,
295+
}
296+
}
268297
}
269298

270299
/// Data to construct a [`BlindedHop`] for sending a payment over.
@@ -392,12 +421,23 @@ impl Writeable for ForwardTlvs {
392421
}
393422

394423
impl Writeable for ReceiveTlvs {
424+
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
425+
encode_tlv_stream!(w, {
426+
(12, self.tlvs.payment_constraints, required),
427+
(65536, self.tlvs.payment_secret, required),
428+
(65537, self.tlvs.payment_context, required),
429+
(65539, self.authentication, required),
430+
});
431+
Ok(())
432+
}
433+
}
434+
435+
impl Writeable for UnauthenticatedReceiveTlvs {
395436
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
396437
encode_tlv_stream!(w, {
397438
(12, self.payment_constraints, required),
398439
(65536, self.payment_secret, required),
399440
(65537, self.payment_context, required),
400-
(65539, self.authentication, option),
401441
});
402442
Ok(())
403443
}
@@ -443,10 +483,12 @@ impl Readable for BlindedPaymentTlvs {
443483
} else {
444484
if payment_relay.is_some() || features.is_some() { return Err(DecodeError::InvalidValue) }
445485
Ok(BlindedPaymentTlvs::Receive(ReceiveTlvs {
446-
payment_secret: payment_secret.ok_or(DecodeError::InvalidValue)?,
447-
payment_constraints: payment_constraints.0.unwrap(),
448-
payment_context: payment_context.ok_or(DecodeError::InvalidValue)?,
449-
authentication,
486+
tlvs: UnauthenticatedReceiveTlvs {
487+
payment_secret: payment_secret.ok_or(DecodeError::InvalidValue)?,
488+
payment_constraints: payment_constraints.0.unwrap(),
489+
payment_context: payment_context.ok_or(DecodeError::InvalidValue)?,
490+
},
491+
authentication: authentication.ok_or(DecodeError::InvalidValue)?,
450492
}))
451493
}
452494
}
@@ -489,7 +531,7 @@ pub(crate) fn amt_to_forward_msat(inbound_amt_msat: u64, payment_relay: &Payment
489531
}
490532

491533
pub(super) fn compute_payinfo(
492-
intermediate_nodes: &[PaymentForwardNode], payee_tlvs: &ReceiveTlvs,
534+
intermediate_nodes: &[PaymentForwardNode], payee_tlvs: &UnauthenticatedReceiveTlvs,
493535
payee_htlc_maximum_msat: u64, min_final_cltv_expiry_delta: u16,
494536
) -> Result<BlindedPayInfo, ()> {
495537
let mut curr_base_fee: u64 = 0;
@@ -613,7 +655,7 @@ impl_writeable_tlv_based!(Bolt12RefundContext, {});
613655
#[cfg(test)]
614656
mod tests {
615657
use bitcoin::secp256k1::PublicKey;
616-
use crate::blinded_path::payment::{Bolt12RefundContext, PaymentForwardNode, ForwardTlvs, ReceiveTlvs, PaymentConstraints, PaymentContext, PaymentRelay};
658+
use crate::blinded_path::payment::{Bolt12RefundContext, PaymentForwardNode, ForwardTlvs, PaymentConstraints, PaymentContext, PaymentRelay, UnauthenticatedReceiveTlvs};
617659
use crate::types::payment::PaymentSecret;
618660
use crate::types::features::BlindedHopFeatures;
619661
use crate::ln::functional_test_utils::TEST_FINAL_CLTV;
@@ -658,14 +700,13 @@ mod tests {
658700
},
659701
htlc_maximum_msat: u64::max_value(),
660702
}];
661-
let recv_tlvs = ReceiveTlvs {
703+
let recv_tlvs = UnauthenticatedReceiveTlvs {
662704
payment_secret: PaymentSecret([0; 32]),
663705
payment_constraints: PaymentConstraints {
664706
max_cltv_expiry: 0,
665707
htlc_minimum_msat: 1,
666708
},
667709
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}),
668-
authentication: None,
669710
};
670711
let htlc_maximum_msat = 100_000;
671712
let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, htlc_maximum_msat, 12).unwrap();
@@ -678,14 +719,13 @@ mod tests {
678719

679720
#[test]
680721
fn compute_payinfo_1_hop() {
681-
let recv_tlvs = ReceiveTlvs {
722+
let recv_tlvs = UnauthenticatedReceiveTlvs {
682723
payment_secret: PaymentSecret([0; 32]),
683724
payment_constraints: PaymentConstraints {
684725
max_cltv_expiry: 0,
685726
htlc_minimum_msat: 1,
686727
},
687728
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}),
688-
authentication: None,
689729
};
690730
let blinded_payinfo = super::compute_payinfo(&[], &recv_tlvs, 4242, TEST_FINAL_CLTV as u16).unwrap();
691731
assert_eq!(blinded_payinfo.fee_base_msat, 0);
@@ -735,14 +775,13 @@ mod tests {
735775
},
736776
htlc_maximum_msat: u64::max_value()
737777
}];
738-
let recv_tlvs = ReceiveTlvs {
778+
let recv_tlvs = UnauthenticatedReceiveTlvs {
739779
payment_secret: PaymentSecret([0; 32]),
740780
payment_constraints: PaymentConstraints {
741781
max_cltv_expiry: 0,
742782
htlc_minimum_msat: 3,
743783
},
744784
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}),
745-
authentication: None,
746785
};
747786
let htlc_maximum_msat = 100_000;
748787
let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, htlc_maximum_msat, TEST_FINAL_CLTV as u16).unwrap();
@@ -789,14 +828,13 @@ mod tests {
789828
},
790829
htlc_maximum_msat: u64::max_value()
791830
}];
792-
let recv_tlvs = ReceiveTlvs {
831+
let recv_tlvs = UnauthenticatedReceiveTlvs {
793832
payment_secret: PaymentSecret([0; 32]),
794833
payment_constraints: PaymentConstraints {
795834
max_cltv_expiry: 0,
796835
htlc_minimum_msat: 1,
797836
},
798837
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}),
799-
authentication: None,
800838
};
801839
let htlc_minimum_msat = 3798;
802840
assert!(super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, htlc_minimum_msat - 1, TEST_FINAL_CLTV as u16).is_err());
@@ -847,14 +885,13 @@ mod tests {
847885
},
848886
htlc_maximum_msat: 10_000
849887
}];
850-
let recv_tlvs = ReceiveTlvs {
888+
let recv_tlvs = UnauthenticatedReceiveTlvs {
851889
payment_secret: PaymentSecret([0; 32]),
852890
payment_constraints: PaymentConstraints {
853891
max_cltv_expiry: 0,
854892
htlc_minimum_msat: 1,
855893
},
856894
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}),
857-
authentication: None,
858895
};
859896

860897
let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, 10_000, TEST_FINAL_CLTV as u16).unwrap();

0 commit comments

Comments
 (0)