Skip to content

Commit 5b29a11

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 322b2a2 commit 5b29a11

File tree

4 files changed

+43
-2
lines changed

4 files changed

+43
-2
lines changed

lightning/src/offers/invoice.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1613,6 +1613,7 @@ mod tests {
16131613
payer_id: Some(&payer_pubkey()),
16141614
payer_note: None,
16151615
paths: None,
1616+
source_human_readable_name: None,
16161617
},
16171618
InvoiceTlvStreamRef {
16181619
paths: Some(Iterable(payment_paths.iter().map(|path| path.inner_blinded_path()))),
@@ -1706,6 +1707,7 @@ mod tests {
17061707
payer_id: Some(&payer_pubkey()),
17071708
payer_note: None,
17081709
paths: None,
1710+
source_human_readable_name: None,
17091711
},
17101712
InvoiceTlvStreamRef {
17111713
paths: Some(Iterable(payment_paths.iter().map(|path| path.inner_blinded_path()))),

lightning/src/offers/invoice_request.rs

Lines changed: 27 additions & 1 deletion
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
InvoiceRequestContentsWithoutPayerSigningPubkey {
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
@@ -643,6 +653,7 @@ pub(super) struct InvoiceRequestContentsWithoutPayerSigningPubkey {
643653
features: InvoiceRequestFeatures,
644654
quantity: Option<u64>,
645655
payer_note: Option<String>,
656+
source_human_readable_name: Option<HumanReadableName>,
646657
}
647658

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

692709
impl UnsignedInvoiceRequest {
@@ -940,7 +957,7 @@ impl VerifiedInvoiceRequest {
940957
let InvoiceRequestContents {
941958
payer_signing_pubkey,
942959
inner: InvoiceRequestContentsWithoutPayerSigningPubkey {
943-
payer: _, offer: _, chain: _, amount_msats: _, features: _, quantity, payer_note
960+
quantity, payer_note, ..
944961
},
945962
} = &self.inner.contents;
946963

@@ -983,6 +1000,10 @@ impl InvoiceRequestContents {
9831000
.map(|payer_note| PrintableString(payer_note.as_str()))
9841001
}
9851002

1003+
pub(super) fn source_human_readable_name(&self) -> &Option<HumanReadableName> {
1004+
&self.inner.source_human_readable_name
1005+
}
1006+
9861007
pub(super) fn as_tlv_stream(&self) -> PartialInvoiceRequestTlvStreamRef {
9871008
let (payer, offer, mut invoice_request) = self.inner.as_tlv_stream();
9881009
invoice_request.payer_id = Some(&self.payer_signing_pubkey);
@@ -1018,6 +1039,7 @@ impl InvoiceRequestContentsWithoutPayerSigningPubkey {
10181039
quantity: self.quantity,
10191040
payer_id: None,
10201041
payer_note: self.payer_note.as_ref(),
1042+
source_human_readable_name: self.source_human_readable_name.as_ref(),
10211043
paths: None,
10221044
};
10231045

@@ -1070,6 +1092,7 @@ tlv_stream!(InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef, INVOICE_REQUEST
10701092
(89, payer_note: (String, WithoutLength)),
10711093
// Only used for Refund since the onion message of an InvoiceRequest has a reply path.
10721094
(90, paths: (Vec<BlindedMessagePath>, WithoutLength)),
1095+
(91, source_human_readable_name: HumanReadableName),
10731096
});
10741097

10751098
type FullInvoiceRequestTlvStream =
@@ -1154,6 +1177,7 @@ impl TryFrom<PartialInvoiceRequestTlvStream> for InvoiceRequestContents {
11541177
offer_tlv_stream,
11551178
InvoiceRequestTlvStream {
11561179
chain, amount, features, quantity, payer_id, payer_note, paths,
1180+
source_human_readable_name,
11571181
},
11581182
) = tlv_stream;
11591183

@@ -1188,6 +1212,7 @@ impl TryFrom<PartialInvoiceRequestTlvStream> for InvoiceRequestContents {
11881212
Ok(InvoiceRequestContents {
11891213
inner: InvoiceRequestContentsWithoutPayerSigningPubkey {
11901214
payer, offer, chain, amount_msats: amount, features, quantity, payer_note,
1215+
source_human_readable_name,
11911216
},
11921217
payer_signing_pubkey,
11931218
})
@@ -1365,6 +1390,7 @@ mod tests {
13651390
payer_id: Some(&payer_pubkey()),
13661391
payer_note: None,
13671392
paths: None,
1393+
source_human_readable_name: None,
13681394
},
13691395
SignatureTlvStreamRef { signature: Some(&invoice_request.signature()) },
13701396
),

lightning/src/offers/parse.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,11 @@ pub enum Bolt12SemanticError {
195195
InvalidSigningPubkey,
196196
/// A signature was expected but was missing.
197197
MissingSignature,
198+
/// A Human Readable Name was provided but was not expected (i.e. was included in a
199+
/// [`Refund`]).
200+
///
201+
/// [`Refund`]: super::refund::Refund
202+
UnexpectedHumanReadableName,
198203
}
199204

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

lightning/src/offers/refund.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -768,6 +768,7 @@ impl RefundContents {
768768
payer_id: Some(&self.payer_signing_pubkey),
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
issuer_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::UnexpectedIssuerSigningPubkey);
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)