Skip to content

Commit 8c1ef52

Browse files
committed
Add support for storing a source HRN in BOLT 12 invoice_requests
When we resolve a Human Readable Name to a BOLT 12 `offer`, we may end up resolving to a wildcard DNS name covering all possible `user` parts. In that case, if we just blindly pay the `offer`, the recipient would have no way to tell which `user` we paid. Instead, BOLT 12 defines a field to include the HRN resolved in the `invoice_request`, which we implement here.
1 parent 0d9ef1f commit 8c1ef52

File tree

4 files changed

+43
-2
lines changed

4 files changed

+43
-2
lines changed

lightning/src/offers/invoice.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1571,6 +1571,7 @@ mod tests {
15711571
payer_id: Some(&payer_pubkey()),
15721572
payer_note: None,
15731573
paths: None,
1574+
source_human_readable_name: None,
15741575
},
15751576
InvoiceTlvStreamRef {
15761577
paths: Some(Iterable(payment_paths.iter().map(|path| path.inner_blinded_path()))),
@@ -1664,6 +1665,7 @@ mod tests {
16641665
payer_id: Some(&payer_pubkey()),
16651666
payer_note: None,
16661667
paths: None,
1668+
source_human_readable_name: None,
16671669
},
16681670
InvoiceTlvStreamRef {
16691671
paths: Some(Iterable(payment_paths.iter().map(|path| path.inner_blinded_path()))),

lightning/src/offers/invoice_request.rs

+27-1
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ use crate::offers::offer::{Offer, OfferContents, OfferId, OfferTlvStream, OfferT
7575
use crate::offers::parse::{Bolt12ParseError, ParsedMessage, Bolt12SemanticError};
7676
use crate::offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef};
7777
use crate::offers::signer::{Metadata, MetadataMaterial};
78+
use crate::onion_message::dns_resolution::HumanReadableName;
7879
use crate::util::ser::{CursorReadable, HighZeroBytesDroppedBigSize, Readable, WithoutLength, Writeable, Writer};
7980
use crate::util::string::{PrintableString, UntrustedString};
8081

@@ -241,6 +242,7 @@ macro_rules! invoice_request_builder_methods { (
241242
InvoiceRequestContentsWithoutPayerId {
242243
payer: PayerContents(metadata), offer, chain: None, amount_msats: None,
243244
features: InvoiceRequestFeatures::empty(), quantity: None, payer_note: None,
245+
source_human_readable_name: None,
244246
}
245247
}
246248

@@ -299,6 +301,14 @@ macro_rules! invoice_request_builder_methods { (
299301
$return_value
300302
}
301303

304+
/// Sets the [`InvoiceRequest::source_human_readable_name`].
305+
///
306+
/// Successive calls to this method will override the previous setting.
307+
pub fn sourced_from_human_readable_name($($self_mut)* $self: $self_type, hrn: HumanReadableName) -> $return_type {
308+
$self.invoice_request.source_human_readable_name = Some(hrn);
309+
$return_value
310+
}
311+
302312
fn build_with_checks($($self_mut)* $self: $self_type) -> Result<
303313
(UnsignedInvoiceRequest, Option<Keypair>, Option<&'b Secp256k1<$secp_context>>),
304314
Bolt12SemanticError
@@ -644,6 +654,7 @@ pub(super) struct InvoiceRequestContentsWithoutPayerId {
644654
features: InvoiceRequestFeatures,
645655
quantity: Option<u64>,
646656
payer_note: Option<String>,
657+
source_human_readable_name: Option<HumanReadableName>,
647658
}
648659

649660
macro_rules! invoice_request_accessors { ($self: ident, $contents: expr) => {
@@ -688,6 +699,12 @@ macro_rules! invoice_request_accessors { ($self: ident, $contents: expr) => {
688699
pub fn payer_note(&$self) -> Option<PrintableString> {
689700
$contents.payer_note()
690701
}
702+
703+
/// If the [`Offer`] was sourced from a BIP 353 Human Readable Name, this should be set by the
704+
/// builder to indicate the original [`HumanReadableName`] which was resolved.
705+
pub fn source_human_readable_name(&$self) -> &Option<HumanReadableName> {
706+
$contents.source_human_readable_name()
707+
}
691708
} }
692709

693710
impl UnsignedInvoiceRequest {
@@ -936,7 +953,7 @@ impl VerifiedInvoiceRequest {
936953
let InvoiceRequestContents {
937954
payer_id,
938955
inner: InvoiceRequestContentsWithoutPayerId {
939-
payer: _, offer: _, chain: _, amount_msats: _, features: _, quantity, payer_note
956+
quantity, payer_note, ..
940957
},
941958
} = &self.inner.contents;
942959

@@ -979,6 +996,10 @@ impl InvoiceRequestContents {
979996
.map(|payer_note| PrintableString(payer_note.as_str()))
980997
}
981998

999+
pub(super) fn source_human_readable_name(&self) -> &Option<HumanReadableName> {
1000+
&self.inner.source_human_readable_name
1001+
}
1002+
9821003
pub(super) fn as_tlv_stream(&self) -> PartialInvoiceRequestTlvStreamRef {
9831004
let (payer, offer, mut invoice_request) = self.inner.as_tlv_stream();
9841005
invoice_request.payer_id = Some(&self.payer_id);
@@ -1014,6 +1035,7 @@ impl InvoiceRequestContentsWithoutPayerId {
10141035
quantity: self.quantity,
10151036
payer_id: None,
10161037
payer_note: self.payer_note.as_ref(),
1038+
source_human_readable_name: self.source_human_readable_name.as_ref(),
10171039
paths: None,
10181040
};
10191041

@@ -1058,6 +1080,7 @@ tlv_stream!(InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef, INVOICE_REQUEST
10581080
(89, payer_note: (String, WithoutLength)),
10591081
// Only used for Refund since the onion message of an InvoiceRequest has a reply path.
10601082
(90, paths: (Vec<BlindedMessagePath>, WithoutLength)),
1083+
(91, source_human_readable_name: HumanReadableName),
10611084
});
10621085

10631086
type FullInvoiceRequestTlvStream =
@@ -1142,6 +1165,7 @@ impl TryFrom<PartialInvoiceRequestTlvStream> for InvoiceRequestContents {
11421165
offer_tlv_stream,
11431166
InvoiceRequestTlvStream {
11441167
chain, amount, features, quantity, payer_id, payer_note, paths,
1168+
source_human_readable_name,
11451169
},
11461170
) = tlv_stream;
11471171

@@ -1176,6 +1200,7 @@ impl TryFrom<PartialInvoiceRequestTlvStream> for InvoiceRequestContents {
11761200
Ok(InvoiceRequestContents {
11771201
inner: InvoiceRequestContentsWithoutPayerId {
11781202
payer, offer, chain, amount_msats: amount, features, quantity, payer_note,
1203+
source_human_readable_name,
11791204
},
11801205
payer_id,
11811206
})
@@ -1353,6 +1378,7 @@ mod tests {
13531378
payer_id: Some(&payer_pubkey()),
13541379
payer_note: None,
13551380
paths: None,
1381+
source_human_readable_name: None,
13561382
},
13571383
SignatureTlvStreamRef { signature: Some(&invoice_request.signature()) },
13581384
),

lightning/src/offers/parse.rs

+5
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,11 @@ pub enum Bolt12SemanticError {
193193
UnexpectedPaymentHash,
194194
/// A signature was expected but was missing.
195195
MissingSignature,
196+
/// A Human Readable Name was provided but was not expected (i.e. was included in a
197+
/// [`Refund`]).
198+
///
199+
/// [`Refund`]: super::refund::Refund
200+
UnexpectedHumanReadableName,
196201
}
197202

198203
impl From<bech32::Error> for Bolt12ParseError {

lightning/src/offers/refund.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -768,6 +768,7 @@ impl RefundContents {
768768
payer_id: Some(&self.payer_id),
769769
payer_note: self.payer_note.as_ref(),
770770
paths: self.paths.as_ref(),
771+
source_human_readable_name: None,
771772
};
772773

773774
(payer, offer, invoice_request)
@@ -847,7 +848,8 @@ impl TryFrom<RefundTlvStream> for RefundContents {
847848
node_id,
848849
},
849850
InvoiceRequestTlvStream {
850-
chain, amount, features, quantity, payer_id, payer_note, paths
851+
chain, amount, features, quantity, payer_id, payer_note, paths,
852+
source_human_readable_name,
851853
},
852854
) = tlv_stream;
853855

@@ -891,6 +893,11 @@ impl TryFrom<RefundTlvStream> for RefundContents {
891893
return Err(Bolt12SemanticError::UnexpectedSigningPubkey);
892894
}
893895

896+
if source_human_readable_name.is_some() {
897+
// Only offers can be resolved using Human Readable Names
898+
return Err(Bolt12SemanticError::UnexpectedHumanReadableName);
899+
}
900+
894901
let amount_msats = match amount {
895902
None => return Err(Bolt12SemanticError::MissingAmount),
896903
Some(amount_msats) if amount_msats > MAX_VALUE_MSAT => {
@@ -1013,6 +1020,7 @@ mod tests {
10131020
payer_id: Some(&payer_pubkey()),
10141021
payer_note: None,
10151022
paths: None,
1023+
source_human_readable_name: None,
10161024
},
10171025
),
10181026
);

0 commit comments

Comments
 (0)