Skip to content

Commit bf42847

Browse files
committed
Elide metadata from Offer with derived keys
When an Offer uses blinded paths, its metadata consists of a nonce used to derive its signing keys. Now that the blinded paths contain this nonce, elide the metadata as it is now redundant. This saves space and also makes it impossible to derive the signing keys if an invoice request is received with the incorrect nonce. The nonce shouldn't be revealed in this case either to prevent de-anonymization attacks.
1 parent 35b75fd commit bf42847

File tree

4 files changed

+55
-26
lines changed

4 files changed

+55
-26
lines changed

lightning/src/offers/invoice.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1776,15 +1776,17 @@ mod tests {
17761776
.sign(payer_sign).unwrap();
17771777

17781778
if let Err(e) = invoice_request.clone()
1779-
.verify(&expanded_key, &secp_ctx).unwrap()
1779+
.verify_using_recipient_data(nonce, &expanded_key, &secp_ctx).unwrap()
17801780
.respond_using_derived_keys_no_std(payment_paths(), payment_hash(), now()).unwrap()
17811781
.build_and_sign(&secp_ctx)
17821782
{
17831783
panic!("error building invoice: {:?}", e);
17841784
}
17851785

17861786
let expanded_key = ExpandedKey::new(&KeyMaterial([41; 32]));
1787-
assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_err());
1787+
assert!(
1788+
invoice_request.verify_using_recipient_data(nonce, &expanded_key, &secp_ctx).is_err()
1789+
);
17881790

17891791
let offer = OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, nonce, &secp_ctx)
17901792
.amount_msats(1000)

lightning/src/offers/invoice_request.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -605,8 +605,9 @@ pub struct InvoiceRequest {
605605
signature: Signature,
606606
}
607607

608-
/// An [`InvoiceRequest`] that has been verified by [`InvoiceRequest::verify`] and exposes different
609-
/// ways to respond depending on whether the signing keys were derived.
608+
/// An [`InvoiceRequest`] that has been verified by [`InvoiceRequest::verify`] or
609+
/// [`InvoiceRequest::verify_using_recipient_data`] and exposes different ways to respond depending
610+
/// on whether the signing keys were derived.
610611
#[derive(Clone, Debug)]
611612
pub struct VerifiedInvoiceRequest {
612613
/// The identifier of the [`Offer`] for which the [`InvoiceRequest`] was made.
@@ -737,7 +738,8 @@ macro_rules! invoice_request_respond_with_explicit_signing_pubkey_methods { (
737738
/// # Note
738739
///
739740
/// If the originating [`Offer`] was created using [`OfferBuilder::deriving_signing_pubkey`],
740-
/// then use [`InvoiceRequest::verify`] and [`VerifiedInvoiceRequest`] methods instead.
741+
/// then first use [`InvoiceRequest::verify`] or [`InvoiceRequest::verify_using_recipient_data`]
742+
/// and then [`VerifiedInvoiceRequest`] methods instead.
741743
///
742744
/// [`Bolt12Invoice::created_at`]: crate::offers::invoice::Bolt12Invoice::created_at
743745
/// [`OfferBuilder::deriving_signing_pubkey`]: crate::offers::offer::OfferBuilder::deriving_signing_pubkey

lightning/src/offers/offer.rs

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -249,9 +249,12 @@ macro_rules! offer_derived_metadata_builder_methods { ($secp_context: ty) => {
249249
///
250250
/// Also, sets the metadata when [`OfferBuilder::build`] is called such that it can be used by
251251
/// [`InvoiceRequest::verify`] to determine if the request was produced for the offer given an
252-
/// [`ExpandedKey`].
252+
/// [`ExpandedKey`]. However, if [`OfferBuilder::path`] is called, then the metadata will not be
253+
/// set and must be included in each [`BlindedPath`] instead. In this case, use
254+
/// [`InvoiceRequest::verify_using_recipient_data`].
253255
///
254256
/// [`InvoiceRequest::verify`]: crate::offers::invoice_request::InvoiceRequest::verify
257+
/// [`InvoiceRequest::verify_using_recipient_data`]: crate::offers::invoice_request::InvoiceRequest::verify_using_recipient_data
255258
/// [`ExpandedKey`]: crate::ln::inbound_payment::ExpandedKey
256259
pub fn deriving_signing_pubkey(
257260
node_id: PublicKey, expanded_key: &ExpandedKey, nonce: Nonce,
@@ -384,9 +387,12 @@ macro_rules! offer_builder_methods { (
384387
}
385388

386389
fn build_without_checks($($self_mut)* $self: $self_type) -> Offer {
387-
// Create the metadata for stateless verification of an InvoiceRequest.
388390
if let Some(mut metadata) = $self.offer.metadata.take() {
391+
// Create the metadata for stateless verification of an InvoiceRequest.
389392
if metadata.has_derivation_material() {
393+
394+
// Don't derive keys if no blinded paths were given since this means the signing
395+
// pubkey must be the node id of an announced node.
390396
if $self.offer.paths.is_none() {
391397
metadata = metadata.without_keys();
392398
}
@@ -398,14 +404,17 @@ macro_rules! offer_builder_methods { (
398404
tlv_stream.node_id = None;
399405
}
400406

407+
// Either replace the signing pubkey with the derived pubkey or include the metadata
408+
// for verification. In the former case, the blinded paths must include
409+
// `OffersContext::InvoiceRequest` instead.
401410
let (derived_metadata, keys) = metadata.derive_from(tlv_stream, $self.secp_ctx);
402-
metadata = derived_metadata;
403-
if let Some(keys) = keys {
404-
$self.offer.signing_pubkey = Some(keys.public_key());
411+
match keys {
412+
Some(keys) => $self.offer.signing_pubkey = Some(keys.public_key()),
413+
None => $self.offer.metadata = Some(derived_metadata),
405414
}
415+
} else {
416+
$self.offer.metadata = Some(metadata);
406417
}
407-
408-
$self.offer.metadata = Some(metadata);
409418
}
410419

411420
let mut bytes = Vec::new();
@@ -667,9 +676,9 @@ impl Offer {
667676

668677
#[cfg(async_payments)]
669678
pub(super) fn verify<T: secp256k1::Signing>(
670-
&self, key: &ExpandedKey, secp_ctx: &Secp256k1<T>
679+
&self, nonce: Nonce, key: &ExpandedKey, secp_ctx: &Secp256k1<T>
671680
) -> Result<(OfferId, Option<Keypair>), ()> {
672-
self.contents.verify(&self.bytes, key, secp_ctx)
681+
self.contents.verify_using_recipient_data(&self.bytes, nonce, key, secp_ctx)
673682
}
674683
}
675684

@@ -1296,6 +1305,7 @@ mod tests {
12961305
let offer = OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, nonce, &secp_ctx)
12971306
.amount_msats(1000)
12981307
.build().unwrap();
1308+
assert!(offer.metadata().is_some());
12991309
assert_eq!(offer.signing_pubkey(), Some(node_id));
13001310

13011311
let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
@@ -1365,23 +1375,22 @@ mod tests {
13651375
.amount_msats(1000)
13661376
.path(blinded_path)
13671377
.build().unwrap();
1378+
assert!(offer.metadata().is_none());
13681379
assert_ne!(offer.signing_pubkey(), Some(node_id));
13691380

13701381
let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
13711382
.build().unwrap()
13721383
.sign(payer_sign).unwrap();
1373-
match invoice_request.verify(&expanded_key, &secp_ctx) {
1384+
match invoice_request.verify_using_recipient_data(nonce, &expanded_key, &secp_ctx) {
13741385
Ok(invoice_request) => assert_eq!(invoice_request.offer_id, offer.id()),
13751386
Err(_) => panic!("unexpected error"),
13761387
}
13771388

1389+
// Fails verification when using the wrong method
13781390
let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
13791391
.build().unwrap()
13801392
.sign(payer_sign).unwrap();
1381-
match invoice_request.verify_using_recipient_data(nonce, &expanded_key, &secp_ctx) {
1382-
Ok(invoice_request) => assert_eq!(invoice_request.offer_id, offer.id()),
1383-
Err(_) => panic!("unexpected error"),
1384-
}
1393+
assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_err());
13851394

13861395
// Fails verification with altered offer field
13871396
let mut tlv_stream = offer.as_tlv_stream();
@@ -1394,7 +1403,9 @@ mod tests {
13941403
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
13951404
.build().unwrap()
13961405
.sign(payer_sign).unwrap();
1397-
assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_err());
1406+
assert!(
1407+
invoice_request.verify_using_recipient_data(nonce, &expanded_key, &secp_ctx).is_err()
1408+
);
13981409

13991410
// Fails verification with altered signing pubkey
14001411
let mut tlv_stream = offer.as_tlv_stream();
@@ -1408,7 +1419,9 @@ mod tests {
14081419
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
14091420
.build().unwrap()
14101421
.sign(payer_sign).unwrap();
1411-
assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_err());
1422+
assert!(
1423+
invoice_request.verify_using_recipient_data(nonce, &expanded_key, &secp_ctx).is_err()
1424+
);
14121425
}
14131426

14141427
#[test]

lightning/src/offers/static_invoice.rs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use crate::offers::invoice_macros::{invoice_accessors_common, invoice_builder_me
2222
use crate::offers::merkle::{
2323
self, SignError, SignFn, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash,
2424
};
25+
use crate::offers::nonce::Nonce;
2526
use crate::offers::offer::{
2627
Amount, Offer, OfferContents, OfferTlvStream, OfferTlvStreamRef, Quantity,
2728
};
@@ -97,7 +98,7 @@ impl<'a> StaticInvoiceBuilder<'a> {
9798
pub fn for_offer_using_derived_keys<T: secp256k1::Signing>(
9899
offer: &'a Offer, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>,
99100
message_paths: Vec<BlindedPath>, created_at: Duration, expanded_key: &ExpandedKey,
100-
secp_ctx: &Secp256k1<T>,
101+
nonce: Nonce, secp_ctx: &Secp256k1<T>,
101102
) -> Result<Self, Bolt12SemanticError> {
102103
if offer.chains().len() > 1 {
103104
return Err(Bolt12SemanticError::UnexpectedChain);
@@ -111,7 +112,7 @@ impl<'a> StaticInvoiceBuilder<'a> {
111112
offer.signing_pubkey().ok_or(Bolt12SemanticError::MissingSigningPubkey)?;
112113

113114
let keys = offer
114-
.verify(&expanded_key, &secp_ctx)
115+
.verify(nonce, &expanded_key, &secp_ctx)
115116
.map_err(|()| Bolt12SemanticError::InvalidMetadata)?
116117
.1
117118
.ok_or(Bolt12SemanticError::MissingSigningPubkey)?;
@@ -623,6 +624,7 @@ mod tests {
623624
vec![blinded_path()],
624625
now,
625626
&expanded_key,
627+
nonce,
626628
&secp_ctx,
627629
)
628630
.unwrap()
@@ -662,6 +664,7 @@ mod tests {
662664
vec![blinded_path()],
663665
now,
664666
&expanded_key,
667+
nonce,
665668
&secp_ctx,
666669
)
667670
.unwrap()
@@ -672,7 +675,7 @@ mod tests {
672675
invoice.write(&mut buffer).unwrap();
673676

674677
assert_eq!(invoice.bytes, buffer.as_slice());
675-
assert!(invoice.metadata().is_some());
678+
assert_eq!(invoice.metadata(), None);
676679
assert_eq!(invoice.amount(), None);
677680
assert_eq!(invoice.description(), None);
678681
assert_eq!(invoice.offer_features(), &OfferFeatures::empty());
@@ -698,13 +701,12 @@ mod tests {
698701
);
699702

700703
let paths = vec![blinded_path()];
701-
let metadata = vec![42; 16];
702704
assert_eq!(
703705
invoice.as_tlv_stream(),
704706
(
705707
OfferTlvStreamRef {
706708
chains: None,
707-
metadata: Some(&metadata),
709+
metadata: None,
708710
currency: None,
709711
amount: None,
710712
description: None,
@@ -762,6 +764,7 @@ mod tests {
762764
vec![blinded_path()],
763765
now,
764766
&expanded_key,
767+
nonce,
765768
&secp_ctx,
766769
)
767770
.unwrap()
@@ -782,6 +785,7 @@ mod tests {
782785
vec![blinded_path()],
783786
now,
784787
&expanded_key,
788+
nonce,
785789
&secp_ctx,
786790
)
787791
.unwrap()
@@ -815,6 +819,7 @@ mod tests {
815819
vec![blinded_path()],
816820
now,
817821
&expanded_key,
822+
nonce,
818823
&secp_ctx,
819824
) {
820825
assert_eq!(e, Bolt12SemanticError::MissingPaths);
@@ -829,6 +834,7 @@ mod tests {
829834
Vec::new(),
830835
now,
831836
&expanded_key,
837+
nonce,
832838
&secp_ctx,
833839
) {
834840
assert_eq!(e, Bolt12SemanticError::MissingPaths);
@@ -849,6 +855,7 @@ mod tests {
849855
vec![blinded_path()],
850856
now,
851857
&expanded_key,
858+
nonce,
852859
&secp_ctx,
853860
) {
854861
assert_eq!(e, Bolt12SemanticError::MissingPaths);
@@ -886,6 +893,7 @@ mod tests {
886893
vec![blinded_path()],
887894
now,
888895
&expanded_key,
896+
nonce,
889897
&secp_ctx,
890898
) {
891899
assert_eq!(e, Bolt12SemanticError::MissingSigningPubkey);
@@ -906,6 +914,7 @@ mod tests {
906914
vec![blinded_path()],
907915
now,
908916
&expanded_key,
917+
nonce,
909918
&secp_ctx,
910919
) {
911920
assert_eq!(e, Bolt12SemanticError::InvalidMetadata);
@@ -937,6 +946,7 @@ mod tests {
937946
vec![blinded_path()],
938947
now,
939948
&expanded_key,
949+
nonce,
940950
&secp_ctx,
941951
) {
942952
assert_eq!(e, Bolt12SemanticError::UnexpectedChain);
@@ -967,6 +977,7 @@ mod tests {
967977
vec![blinded_path()],
968978
now,
969979
&expanded_key,
980+
nonce,
970981
&secp_ctx,
971982
)
972983
.unwrap()
@@ -1007,6 +1018,7 @@ mod tests {
10071018
vec![blinded_path()],
10081019
now,
10091020
&expanded_key,
1021+
nonce,
10101022
&secp_ctx,
10111023
)
10121024
.unwrap()

0 commit comments

Comments
 (0)