Skip to content

Commit e61001f

Browse files
committed
Only require description when offer has an amount
The spec was changed to allow excluding an offer description if the offer doesn't have an amount. However, it is still required when the amount is set.
1 parent db7e696 commit e61001f

File tree

3 files changed

+48
-29
lines changed

3 files changed

+48
-29
lines changed

lightning/src/offers/invoice.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -717,7 +717,7 @@ macro_rules! invoice_accessors { ($self: ident, $contents: expr) => {
717717
/// From [`Offer::description`] or [`Refund::description`].
718718
///
719719
/// [`Offer::description`]: crate::offers::offer::Offer::description
720-
pub fn description(&$self) -> PrintableString {
720+
pub fn description(&$self) -> Option<PrintableString> {
721721
$contents.description()
722722
}
723723

@@ -952,12 +952,12 @@ impl InvoiceContents {
952952
}
953953
}
954954

955-
fn description(&self) -> PrintableString {
955+
fn description(&self) -> Option<PrintableString> {
956956
match self {
957957
InvoiceContents::ForOffer { invoice_request, .. } => {
958958
invoice_request.inner.offer.description()
959959
},
960-
InvoiceContents::ForRefund { refund, .. } => refund.description(),
960+
InvoiceContents::ForRefund { refund, .. } => Some(refund.description()),
961961
}
962962
}
963963

@@ -1546,7 +1546,7 @@ mod tests {
15461546
assert_eq!(unsigned_invoice.offer_chains(), Some(vec![ChainHash::using_genesis_block(Network::Bitcoin)]));
15471547
assert_eq!(unsigned_invoice.metadata(), None);
15481548
assert_eq!(unsigned_invoice.amount(), Some(&Amount::Bitcoin { amount_msats: 1000 }));
1549-
assert_eq!(unsigned_invoice.description(), PrintableString(""));
1549+
assert_eq!(unsigned_invoice.description(), Some(PrintableString("")));
15501550
assert_eq!(unsigned_invoice.offer_features(), Some(&OfferFeatures::empty()));
15511551
assert_eq!(unsigned_invoice.absolute_expiry(), None);
15521552
assert_eq!(unsigned_invoice.message_paths(), &[]);
@@ -1590,7 +1590,7 @@ mod tests {
15901590
assert_eq!(invoice.offer_chains(), Some(vec![ChainHash::using_genesis_block(Network::Bitcoin)]));
15911591
assert_eq!(invoice.metadata(), None);
15921592
assert_eq!(invoice.amount(), Some(&Amount::Bitcoin { amount_msats: 1000 }));
1593-
assert_eq!(invoice.description(), PrintableString(""));
1593+
assert_eq!(invoice.description(), Some(PrintableString("")));
15941594
assert_eq!(invoice.offer_features(), Some(&OfferFeatures::empty()));
15951595
assert_eq!(invoice.absolute_expiry(), None);
15961596
assert_eq!(invoice.message_paths(), &[]);
@@ -1688,7 +1688,7 @@ mod tests {
16881688
assert_eq!(invoice.offer_chains(), None);
16891689
assert_eq!(invoice.metadata(), None);
16901690
assert_eq!(invoice.amount(), None);
1691-
assert_eq!(invoice.description(), PrintableString(""));
1691+
assert_eq!(invoice.description(), Some(PrintableString("")));
16921692
assert_eq!(invoice.offer_features(), None);
16931693
assert_eq!(invoice.absolute_expiry(), None);
16941694
assert_eq!(invoice.message_paths(), &[]);

lightning/src/offers/invoice_request.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1263,7 +1263,7 @@ mod tests {
12631263
assert_eq!(unsigned_invoice_request.chains(), vec![ChainHash::using_genesis_block(Network::Bitcoin)]);
12641264
assert_eq!(unsigned_invoice_request.metadata(), None);
12651265
assert_eq!(unsigned_invoice_request.amount(), Some(&Amount::Bitcoin { amount_msats: 1000 }));
1266-
assert_eq!(unsigned_invoice_request.description(), PrintableString(""));
1266+
assert_eq!(unsigned_invoice_request.description(), Some(PrintableString("")));
12671267
assert_eq!(unsigned_invoice_request.offer_features(), &OfferFeatures::empty());
12681268
assert_eq!(unsigned_invoice_request.absolute_expiry(), None);
12691269
assert_eq!(unsigned_invoice_request.paths(), &[]);
@@ -1295,7 +1295,7 @@ mod tests {
12951295
assert_eq!(invoice_request.chains(), vec![ChainHash::using_genesis_block(Network::Bitcoin)]);
12961296
assert_eq!(invoice_request.metadata(), None);
12971297
assert_eq!(invoice_request.amount(), Some(&Amount::Bitcoin { amount_msats: 1000 }));
1298-
assert_eq!(invoice_request.description(), PrintableString(""));
1298+
assert_eq!(invoice_request.description(), Some(PrintableString("")));
12991299
assert_eq!(invoice_request.offer_features(), &OfferFeatures::empty());
13001300
assert_eq!(invoice_request.absolute_expiry(), None);
13011301
assert_eq!(invoice_request.paths(), &[]);
@@ -1996,6 +1996,7 @@ mod tests {
19961996
}
19971997

19981998
let invoice_request = OfferBuilder::new(recipient_pubkey())
1999+
.description("foo".to_string())
19992000
.amount(Amount::Currency { iso4217_code: *b"USD", amount: 1000 })
20002001
.build_unchecked()
20012002
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()

lightning/src/offers/offer.rs

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -209,9 +209,8 @@ impl MetadataStrategy for DerivedMetadata {}
209209
macro_rules! offer_explicit_metadata_builder_methods { (
210210
$self: ident, $self_type: ty, $return_type: ty, $return_value: expr
211211
) => {
212-
/// Creates a new builder for an offer setting an empty [`Offer::description`] and using the
213-
/// [`Offer::signing_pubkey`] for signing invoices. The associated secret key must be remembered
214-
/// while the offer is valid.
212+
/// Creates a new builder for an offer using the [`Offer::signing_pubkey`] for signing invoices.
213+
/// The associated secret key must be remembered while the offer is valid.
215214
///
216215
/// Use a different pubkey per offer to avoid correlating offers.
217216
///
@@ -225,7 +224,7 @@ macro_rules! offer_explicit_metadata_builder_methods { (
225224
pub fn new(signing_pubkey: PublicKey) -> Self {
226225
Self {
227226
offer: OfferContents {
228-
chains: None, metadata: None, amount: None, description: String::new(),
227+
chains: None, metadata: None, amount: None, description: None,
229228
features: OfferFeatures::empty(), absolute_expiry: None, issuer: None, paths: None,
230229
supported_quantity: Quantity::One, signing_pubkey: Some(signing_pubkey),
231230
},
@@ -264,7 +263,7 @@ macro_rules! offer_derived_metadata_builder_methods { ($secp_context: ty) => {
264263
let metadata = Metadata::DerivedSigningPubkey(derivation_material);
265264
Self {
266265
offer: OfferContents {
267-
chains: None, metadata: Some(metadata), amount: None, description: String::new(),
266+
chains: None, metadata: Some(metadata), amount: None, description: None,
268267
features: OfferFeatures::empty(), absolute_expiry: None, issuer: None, paths: None,
269268
supported_quantity: Quantity::One, signing_pubkey: Some(node_id),
270269
},
@@ -330,7 +329,7 @@ macro_rules! offer_builder_methods { (
330329
///
331330
/// Successive calls to this method will override the previous setting.
332331
pub fn description($($self_mut)* $self: $self_type, description: String) -> $return_type {
333-
$self.offer.description = description;
332+
$self.offer.description = Some(description);
334333
$return_value
335334
}
336335

@@ -373,6 +372,10 @@ macro_rules! offer_builder_methods { (
373372
None => {},
374373
}
375374

375+
if $self.offer.amount.is_some() && $self.offer.description.is_none() {
376+
$self.offer.description = Some(String::new());
377+
}
378+
376379
if let Some(chains) = &$self.offer.chains {
377380
if chains.len() == 1 && chains[0] == $self.offer.implied_chain() {
378381
$self.offer.chains = None;
@@ -551,7 +554,7 @@ pub(super) struct OfferContents {
551554
chains: Option<Vec<ChainHash>>,
552555
metadata: Option<Metadata>,
553556
amount: Option<Amount>,
554-
description: String,
557+
description: Option<String>,
555558
features: OfferFeatures,
556559
absolute_expiry: Option<Duration>,
557560
issuer: Option<String>,
@@ -585,7 +588,7 @@ macro_rules! offer_accessors { ($self: ident, $contents: expr) => {
585588

586589
/// A complete description of the purpose of the payment. Intended to be displayed to the user
587590
/// but with the caveat that it has not been verified in any way.
588-
pub fn description(&$self) -> $crate::util::string::PrintableString {
591+
pub fn description(&$self) -> Option<$crate::util::string::PrintableString> {
589592
$contents.description()
590593
}
591594

@@ -809,8 +812,8 @@ impl OfferContents {
809812
self.amount.as_ref()
810813
}
811814

812-
pub fn description(&self) -> PrintableString {
813-
PrintableString(&self.description)
815+
pub fn description(&self) -> Option<PrintableString> {
816+
self.description.as_ref().map(|description| PrintableString(description))
814817
}
815818

816819
pub fn features(&self) -> &OfferFeatures {
@@ -954,7 +957,7 @@ impl OfferContents {
954957
metadata: self.metadata(),
955958
currency,
956959
amount,
957-
description: Some(&self.description),
960+
description: self.description.as_ref(),
958961
features,
959962
absolute_expiry: self.absolute_expiry.map(|duration| duration.as_secs()),
960963
paths: self.paths.as_ref(),
@@ -1092,10 +1095,9 @@ impl TryFrom<OfferTlvStream> for OfferContents {
10921095
(Some(iso4217_code), Some(amount)) => Some(Amount::Currency { iso4217_code, amount }),
10931096
};
10941097

1095-
let description = match description {
1096-
None => return Err(Bolt12SemanticError::MissingDescription),
1097-
Some(description) => description,
1098-
};
1098+
if amount.is_some() && description.is_none() {
1099+
return Err(Bolt12SemanticError::MissingDescription);
1100+
}
10991101

11001102
let features = features.unwrap_or_else(OfferFeatures::empty);
11011103

@@ -1166,7 +1168,7 @@ mod tests {
11661168
assert!(offer.supports_chain(ChainHash::using_genesis_block(Network::Bitcoin)));
11671169
assert_eq!(offer.metadata(), None);
11681170
assert_eq!(offer.amount(), None);
1169-
assert_eq!(offer.description(), PrintableString(""));
1171+
assert_eq!(offer.description(), None);
11701172
assert_eq!(offer.offer_features(), &OfferFeatures::empty());
11711173
assert_eq!(offer.absolute_expiry(), None);
11721174
#[cfg(feature = "std")]
@@ -1183,7 +1185,7 @@ mod tests {
11831185
metadata: None,
11841186
currency: None,
11851187
amount: None,
1186-
description: Some(&String::from("")),
1188+
description: None,
11871189
features: None,
11881190
absolute_expiry: None,
11891191
paths: None,
@@ -1421,16 +1423,23 @@ mod tests {
14211423
.description("foo".into())
14221424
.build()
14231425
.unwrap();
1424-
assert_eq!(offer.description(), PrintableString("foo"));
1426+
assert_eq!(offer.description(), Some(PrintableString("foo")));
14251427
assert_eq!(offer.as_tlv_stream().description, Some(&String::from("foo")));
14261428

14271429
let offer = OfferBuilder::new(pubkey(42))
14281430
.description("foo".into())
14291431
.description("bar".into())
14301432
.build()
14311433
.unwrap();
1432-
assert_eq!(offer.description(), PrintableString("bar"));
1434+
assert_eq!(offer.description(), Some(PrintableString("bar")));
14331435
assert_eq!(offer.as_tlv_stream().description, Some(&String::from("bar")));
1436+
1437+
let offer = OfferBuilder::new(pubkey(42))
1438+
.amount_msats(1000)
1439+
.build()
1440+
.unwrap();
1441+
assert_eq!(offer.description(), Some(PrintableString("")));
1442+
assert_eq!(offer.as_tlv_stream().description, Some(&String::from("")));
14341443
}
14351444

14361445
#[test]
@@ -1655,6 +1664,14 @@ mod tests {
16551664
panic!("error parsing offer: {:?}", e);
16561665
}
16571666

1667+
let offer = OfferBuilder::new(pubkey(42))
1668+
.description("foo".to_string())
1669+
.amount_msats(1000)
1670+
.build().unwrap();
1671+
if let Err(e) = offer.to_string().parse::<Offer>() {
1672+
panic!("error parsing offer: {:?}", e);
1673+
}
1674+
16581675
let mut tlv_stream = offer.as_tlv_stream();
16591676
tlv_stream.description = None;
16601677

@@ -1875,7 +1892,7 @@ mod bolt12_tests {
18751892
// Malformed: empty
18761893
assert_eq!(
18771894
"lno1".parse::<Offer>(),
1878-
Err(Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingDescription)),
1895+
Err(Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingSigningPubkey)),
18791896
);
18801897

18811898
// Malformed: truncated at type
@@ -2000,7 +2017,8 @@ mod bolt12_tests {
20002017

20012018
// Missing offer_description
20022019
assert_eq!(
2003-
"lno1zcss9mk8y3wkklfvevcrszlmu23kfrxh49px20665dqwmn4p72pksese".parse::<Offer>(),
2020+
// TODO: Match the spec once it is updated.
2021+
"lno1pqpq86qkyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg".parse::<Offer>(),
20042022
Err(Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingDescription)),
20052023
);
20062024

0 commit comments

Comments
 (0)