Skip to content

Commit 4e38002

Browse files
committed
Include PaymentId in payer metadata
When receiving a BOLT 12 invoice originating from either an invoice request or a refund, the invoice should only be paid once. To accomplish this, require that the invoice includes an encrypted payment id in the payer metadata. This allows ChannelManager to track a payment when requesting but prior to receiving the invoice. Thus, it can determine if the invoice has already been paid.
1 parent 230a3e6 commit 4e38002

File tree

7 files changed

+206
-54
lines changed

7 files changed

+206
-54
lines changed

lightning/src/ln/channelmanager.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,12 @@ impl From<&ClaimableHTLC> for events::ClaimedHTLC {
237237
///
238238
/// This is not exported to bindings users as we just use [u8; 32] directly
239239
#[derive(Hash, Copy, Clone, PartialEq, Eq, Debug)]
240-
pub struct PaymentId(pub [u8; 32]);
240+
pub struct PaymentId(pub [u8; Self::LENGTH]);
241+
242+
impl PaymentId {
243+
/// Number of bytes in the id.
244+
pub const LENGTH: usize = 32;
245+
}
241246

242247
impl Writeable for PaymentId {
243248
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {

lightning/src/ln/inbound_payment.rs

+11
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,17 @@ impl ExpandedKey {
7878
hmac.input(&nonce.0);
7979
hmac
8080
}
81+
82+
/// Encrypts or decrypts the given `bytes`. Used for data included in an offer message's
83+
/// metadata (e.g., payment id).
84+
pub(crate) fn crypt_for_offer(&self, mut bytes: [u8; 32], iv_bytes: &[u8; IV_LEN]) -> [u8; 32] {
85+
let chacha_block = ChaCha20::get_single_block(&self.offers_base_key, iv_bytes);
86+
for i in 0..bytes.len() {
87+
bytes[i] = chacha_block[i] ^ bytes[i];
88+
}
89+
90+
bytes
91+
}
8192
}
8293

8394
/// A 128-bit number used only once.

lightning/src/offers/invoice.rs

+6-7
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ use core::time::Duration;
110110
use crate::io;
111111
use crate::blinded_path::BlindedPath;
112112
use crate::ln::PaymentHash;
113+
use crate::ln::channelmanager::PaymentId;
113114
use crate::ln::features::{BlindedHopFeatures, Bolt12InvoiceFeatures, InvoiceRequestFeatures, OfferFeatures};
114115
use crate::ln::inbound_payment::ExpandedKey;
115116
use crate::ln::msgs::DecodeError;
@@ -695,10 +696,11 @@ impl Bolt12Invoice {
695696
merkle::message_digest(SIGNATURE_TAG, &self.bytes).as_ref().clone()
696697
}
697698

698-
/// Verifies that the invoice was for a request or refund created using the given key.
699+
/// Verifies that the invoice was for a request or refund created using the given key. Returns
700+
/// the associated [`PaymentId`] to use when sending the payment.
699701
pub fn verify<T: secp256k1::Signing>(
700702
&self, key: &ExpandedKey, secp_ctx: &Secp256k1<T>
701-
) -> bool {
703+
) -> Result<PaymentId, ()> {
702704
self.contents.verify(TlvStream::new(&self.bytes), key, secp_ctx)
703705
}
704706

@@ -947,7 +949,7 @@ impl InvoiceContents {
947949

948950
fn verify<T: secp256k1::Signing>(
949951
&self, tlv_stream: TlvStream<'_>, key: &ExpandedKey, secp_ctx: &Secp256k1<T>
950-
) -> bool {
952+
) -> Result<PaymentId, ()> {
951953
let offer_records = tlv_stream.clone().range(OFFER_TYPES);
952954
let invreq_records = tlv_stream.range(INVOICE_REQUEST_TYPES).filter(|record| {
953955
match record.r#type {
@@ -967,10 +969,7 @@ impl InvoiceContents {
967969
},
968970
};
969971

970-
match signer::verify_metadata(metadata, key, iv_bytes, payer_id, tlv_stream, secp_ctx) {
971-
Ok(_) => true,
972-
Err(()) => false,
973-
}
972+
signer::verify_payer_metadata(metadata, key, iv_bytes, payer_id, tlv_stream, secp_ctx)
974973
}
975974

976975
fn derives_keys(&self) -> bool {

lightning/src/offers/invoice_request.rs

+28-14
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ use crate::sign::EntropySource;
6464
use crate::io;
6565
use crate::blinded_path::BlindedPath;
6666
use crate::ln::PaymentHash;
67+
use crate::ln::channelmanager::PaymentId;
6768
use crate::ln::features::InvoiceRequestFeatures;
6869
use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce};
6970
use crate::ln::msgs::DecodeError;
@@ -128,10 +129,12 @@ impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, ExplicitPayerI
128129
}
129130

130131
pub(super) fn deriving_metadata<ES: Deref>(
131-
offer: &'a Offer, payer_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES
132+
offer: &'a Offer, payer_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES,
133+
payment_id: PaymentId,
132134
) -> Self where ES::Target: EntropySource {
133135
let nonce = Nonce::from_entropy_source(entropy_source);
134-
let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES);
136+
let payment_id = Some(payment_id);
137+
let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES, payment_id);
135138
let metadata = Metadata::Derived(derivation_material);
136139
Self {
137140
offer,
@@ -145,10 +148,12 @@ impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, ExplicitPayerI
145148

146149
impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, DerivedPayerId, T> {
147150
pub(super) fn deriving_payer_id<ES: Deref>(
148-
offer: &'a Offer, expanded_key: &ExpandedKey, entropy_source: ES, secp_ctx: &'b Secp256k1<T>
151+
offer: &'a Offer, expanded_key: &ExpandedKey, entropy_source: ES,
152+
secp_ctx: &'b Secp256k1<T>, payment_id: PaymentId
149153
) -> Self where ES::Target: EntropySource {
150154
let nonce = Nonce::from_entropy_source(entropy_source);
151-
let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES);
155+
let payment_id = Some(payment_id);
156+
let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES, payment_id);
152157
let metadata = Metadata::DerivedSigningPubkey(derivation_material);
153158
Self {
154159
offer,
@@ -259,7 +264,7 @@ impl<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signing> InvoiceRequestBuilder<'a
259264
let mut tlv_stream = self.invoice_request.as_tlv_stream();
260265
debug_assert!(tlv_stream.2.payer_id.is_none());
261266
tlv_stream.0.metadata = None;
262-
if !metadata.derives_keys() {
267+
if !metadata.derives_payer_keys() {
263268
tlv_stream.2.payer_id = self.payer_id.as_ref();
264269
}
265270

@@ -680,7 +685,7 @@ impl InvoiceRequestContents {
680685
}
681686

682687
pub(super) fn derives_keys(&self) -> bool {
683-
self.inner.payer.0.derives_keys()
688+
self.inner.payer.0.derives_payer_keys()
684689
}
685690

686691
pub(super) fn chain(&self) -> ChainHash {
@@ -913,6 +918,7 @@ mod tests {
913918
#[cfg(feature = "std")]
914919
use core::time::Duration;
915920
use crate::sign::KeyMaterial;
921+
use crate::ln::channelmanager::PaymentId;
916922
use crate::ln::features::{InvoiceRequestFeatures, OfferFeatures};
917923
use crate::ln::inbound_payment::ExpandedKey;
918924
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
@@ -1058,12 +1064,13 @@ mod tests {
10581064
let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
10591065
let entropy = FixedEntropy {};
10601066
let secp_ctx = Secp256k1::new();
1067+
let payment_id = PaymentId([1; 32]);
10611068

10621069
let offer = OfferBuilder::new("foo".into(), recipient_pubkey())
10631070
.amount_msats(1000)
10641071
.build().unwrap();
10651072
let invoice_request = offer
1066-
.request_invoice_deriving_metadata(payer_id, &expanded_key, &entropy)
1073+
.request_invoice_deriving_metadata(payer_id, &expanded_key, &entropy, payment_id)
10671074
.unwrap()
10681075
.build().unwrap()
10691076
.sign(payer_sign).unwrap();
@@ -1073,7 +1080,10 @@ mod tests {
10731080
.unwrap()
10741081
.build().unwrap()
10751082
.sign(recipient_sign).unwrap();
1076-
assert!(invoice.verify(&expanded_key, &secp_ctx));
1083+
match invoice.verify(&expanded_key, &secp_ctx) {
1084+
Ok(payment_id) => assert_eq!(payment_id, PaymentId([1; 32])),
1085+
Err(()) => panic!("verification failed"),
1086+
}
10771087

10781088
// Fails verification with altered fields
10791089
let (
@@ -1096,7 +1106,7 @@ mod tests {
10961106
signature_tlv_stream.write(&mut encoded_invoice).unwrap();
10971107

10981108
let invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap();
1099-
assert!(!invoice.verify(&expanded_key, &secp_ctx));
1109+
assert!(invoice.verify(&expanded_key, &secp_ctx).is_err());
11001110

11011111
// Fails verification with altered metadata
11021112
let (
@@ -1119,20 +1129,21 @@ mod tests {
11191129
signature_tlv_stream.write(&mut encoded_invoice).unwrap();
11201130

11211131
let invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap();
1122-
assert!(!invoice.verify(&expanded_key, &secp_ctx));
1132+
assert!(invoice.verify(&expanded_key, &secp_ctx).is_err());
11231133
}
11241134

11251135
#[test]
11261136
fn builds_invoice_request_with_derived_payer_id() {
11271137
let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
11281138
let entropy = FixedEntropy {};
11291139
let secp_ctx = Secp256k1::new();
1140+
let payment_id = PaymentId([1; 32]);
11301141

11311142
let offer = OfferBuilder::new("foo".into(), recipient_pubkey())
11321143
.amount_msats(1000)
11331144
.build().unwrap();
11341145
let invoice_request = offer
1135-
.request_invoice_deriving_payer_id(&expanded_key, &entropy, &secp_ctx)
1146+
.request_invoice_deriving_payer_id(&expanded_key, &entropy, &secp_ctx, payment_id)
11361147
.unwrap()
11371148
.build_and_sign()
11381149
.unwrap();
@@ -1141,7 +1152,10 @@ mod tests {
11411152
.unwrap()
11421153
.build().unwrap()
11431154
.sign(recipient_sign).unwrap();
1144-
assert!(invoice.verify(&expanded_key, &secp_ctx));
1155+
match invoice.verify(&expanded_key, &secp_ctx) {
1156+
Ok(payment_id) => assert_eq!(payment_id, PaymentId([1; 32])),
1157+
Err(()) => panic!("verification failed"),
1158+
}
11451159

11461160
// Fails verification with altered fields
11471161
let (
@@ -1164,7 +1178,7 @@ mod tests {
11641178
signature_tlv_stream.write(&mut encoded_invoice).unwrap();
11651179

11661180
let invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap();
1167-
assert!(!invoice.verify(&expanded_key, &secp_ctx));
1181+
assert!(invoice.verify(&expanded_key, &secp_ctx).is_err());
11681182

11691183
// Fails verification with altered payer id
11701184
let (
@@ -1187,7 +1201,7 @@ mod tests {
11871201
signature_tlv_stream.write(&mut encoded_invoice).unwrap();
11881202

11891203
let invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap();
1190-
assert!(!invoice.verify(&expanded_key, &secp_ctx));
1204+
assert!(invoice.verify(&expanded_key, &secp_ctx).is_err());
11911205
}
11921206

11931207
#[test]

lightning/src/offers/offer.rs

+23-12
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ use core::time::Duration;
7777
use crate::sign::EntropySource;
7878
use crate::io;
7979
use crate::blinded_path::BlindedPath;
80+
use crate::ln::channelmanager::PaymentId;
8081
use crate::ln::features::OfferFeatures;
8182
use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce};
8283
use crate::ln::msgs::MAX_VALUE_MSAT;
@@ -169,7 +170,7 @@ impl<'a, T: secp256k1::Signing> OfferBuilder<'a, DerivedMetadata, T> {
169170
secp_ctx: &'a Secp256k1<T>
170171
) -> Self where ES::Target: EntropySource {
171172
let nonce = Nonce::from_entropy_source(entropy_source);
172-
let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES);
173+
let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES, None);
173174
let metadata = Metadata::DerivedSigningPubkey(derivation_material);
174175
OfferBuilder {
175176
offer: OfferContents {
@@ -283,7 +284,7 @@ impl<'a, M: MetadataStrategy, T: secp256k1::Signing> OfferBuilder<'a, M, T> {
283284
let mut tlv_stream = self.offer.as_tlv_stream();
284285
debug_assert_eq!(tlv_stream.metadata, None);
285286
tlv_stream.metadata = None;
286-
if metadata.derives_keys() {
287+
if metadata.derives_recipient_keys() {
287288
tlv_stream.node_id = None;
288289
}
289290

@@ -454,10 +455,12 @@ impl Offer {
454455

455456
/// Similar to [`Offer::request_invoice`] except it:
456457
/// - derives the [`InvoiceRequest::payer_id`] such that a different key can be used for each
457-
/// request, and
458-
/// - sets the [`InvoiceRequest::payer_metadata`] when [`InvoiceRequestBuilder::build`] is
459-
/// called such that it can be used by [`Bolt12Invoice::verify`] to determine if the invoice
460-
/// was requested using a base [`ExpandedKey`] from which the payer id was derived.
458+
/// request,
459+
/// - sets [`InvoiceRequest::payer_metadata`] when [`InvoiceRequestBuilder::build`] is called
460+
/// such that it can be used by [`Bolt12Invoice::verify`] to determine if the invoice was
461+
/// requested using a base [`ExpandedKey`] from which the payer id was derived, and
462+
/// - includes the [`PaymentId`] encrypted in [`InvoiceRequest::payer_metadata`] so that it can
463+
/// be used when sending the payment for the requested invoice.
461464
///
462465
/// Useful to protect the sender's privacy.
463466
///
@@ -468,7 +471,8 @@ impl Offer {
468471
/// [`Bolt12Invoice::verify`]: crate::offers::invoice::Bolt12Invoice::verify
469472
/// [`ExpandedKey`]: crate::ln::inbound_payment::ExpandedKey
470473
pub fn request_invoice_deriving_payer_id<'a, 'b, ES: Deref, T: secp256k1::Signing>(
471-
&'a self, expanded_key: &ExpandedKey, entropy_source: ES, secp_ctx: &'b Secp256k1<T>
474+
&'a self, expanded_key: &ExpandedKey, entropy_source: ES, secp_ctx: &'b Secp256k1<T>,
475+
payment_id: PaymentId
472476
) -> Result<InvoiceRequestBuilder<'a, 'b, DerivedPayerId, T>, Bolt12SemanticError>
473477
where
474478
ES::Target: EntropySource,
@@ -477,7 +481,9 @@ impl Offer {
477481
return Err(Bolt12SemanticError::UnknownRequiredFeatures);
478482
}
479483

480-
Ok(InvoiceRequestBuilder::deriving_payer_id(self, expanded_key, entropy_source, secp_ctx))
484+
Ok(InvoiceRequestBuilder::deriving_payer_id(
485+
self, expanded_key, entropy_source, secp_ctx, payment_id
486+
))
481487
}
482488

483489
/// Similar to [`Offer::request_invoice_deriving_payer_id`] except uses `payer_id` for the
@@ -489,7 +495,8 @@ impl Offer {
489495
///
490496
/// [`InvoiceRequest::payer_id`]: crate::offers::invoice_request::InvoiceRequest::payer_id
491497
pub fn request_invoice_deriving_metadata<ES: Deref>(
492-
&self, payer_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES
498+
&self, payer_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES,
499+
payment_id: PaymentId
493500
) -> Result<InvoiceRequestBuilder<ExplicitPayerId, secp256k1::SignOnly>, Bolt12SemanticError>
494501
where
495502
ES::Target: EntropySource,
@@ -498,7 +505,9 @@ impl Offer {
498505
return Err(Bolt12SemanticError::UnknownRequiredFeatures);
499506
}
500507

501-
Ok(InvoiceRequestBuilder::deriving_metadata(self, payer_id, expanded_key, entropy_source))
508+
Ok(InvoiceRequestBuilder::deriving_metadata(
509+
self, payer_id, expanded_key, entropy_source, payment_id
510+
))
502511
}
503512

504513
/// Creates an [`InvoiceRequestBuilder`] for the offer with the given `metadata` and `payer_id`,
@@ -661,11 +670,13 @@ impl OfferContents {
661670
let tlv_stream = TlvStream::new(bytes).range(OFFER_TYPES).filter(|record| {
662671
match record.r#type {
663672
OFFER_METADATA_TYPE => false,
664-
OFFER_NODE_ID_TYPE => !self.metadata.as_ref().unwrap().derives_keys(),
673+
OFFER_NODE_ID_TYPE => {
674+
!self.metadata.as_ref().unwrap().derives_recipient_keys()
675+
},
665676
_ => true,
666677
}
667678
});
668-
signer::verify_metadata(
679+
signer::verify_recipient_metadata(
669680
metadata, key, IV_BYTES, self.signing_pubkey(), tlv_stream, secp_ctx
670681
)
671682
},

0 commit comments

Comments
 (0)