]> git.bitcoin.ninja Git - rust-lightning/commitdiff
Add support for storing a source HRN in BOLT 12 `invoice_request`s
authorMatt Corallo <git@bluematt.me>
Sat, 13 Jul 2024 14:04:39 +0000 (14:04 +0000)
committerMatt Corallo <git@bluematt.me>
Tue, 12 Nov 2024 15:53:12 +0000 (15:53 +0000)
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.

We also take this opportunity to remove constant parameters from
the `outbound_payment.rs` interface to `channelmanager.rs`

lightning/src/offers/invoice.rs
lightning/src/offers/invoice_request.rs
lightning/src/offers/parse.rs
lightning/src/offers/refund.rs

index b1d7660f2240764b207e06dd9913957fbbb0013b..e7e4596901ba0f618939f30cb9e171b635a1f1fa 100644 (file)
@@ -1766,6 +1766,7 @@ mod tests {
                                        payer_id: Some(&payer_pubkey()),
                                        payer_note: None,
                                        paths: None,
+                                       offer_from_hrn: None,
                                },
                                InvoiceTlvStreamRef {
                                        paths: Some(Iterable(payment_paths.iter().map(|path| path.inner_blinded_path()))),
@@ -1868,6 +1869,7 @@ mod tests {
                                        payer_id: Some(&payer_pubkey()),
                                        payer_note: None,
                                        paths: None,
+                                       offer_from_hrn: None,
                                },
                                InvoiceTlvStreamRef {
                                        paths: Some(Iterable(payment_paths.iter().map(|path| path.inner_blinded_path()))),
index d1ab6d067d90d88ef84156763fbe8ff878998b29..c269eb01b479780147955299b04cc0b7ee0f3bc9 100644 (file)
@@ -75,6 +75,7 @@ use crate::offers::offer::{EXPERIMENTAL_OFFER_TYPES, ExperimentalOfferTlvStream,
 use crate::offers::parse::{Bolt12ParseError, ParsedMessage, Bolt12SemanticError};
 use crate::offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef};
 use crate::offers::signer::{Metadata, MetadataMaterial};
+use crate::onion_message::dns_resolution::HumanReadableName;
 use crate::util::ser::{CursorReadable, HighZeroBytesDroppedBigSize, Readable, WithoutLength, Writeable, Writer};
 use crate::util::string::{PrintableString, UntrustedString};
 
@@ -241,6 +242,7 @@ macro_rules! invoice_request_builder_methods { (
                InvoiceRequestContentsWithoutPayerSigningPubkey {
                        payer: PayerContents(metadata), offer, chain: None, amount_msats: None,
                        features: InvoiceRequestFeatures::empty(), quantity: None, payer_note: None,
+                       offer_from_hrn: None,
                        #[cfg(test)]
                        experimental_bar: None,
                }
@@ -301,6 +303,14 @@ macro_rules! invoice_request_builder_methods { (
                $return_value
        }
 
+       /// Sets the [`InvoiceRequest::offer_from_hrn`].
+       ///
+       /// Successive calls to this method will override the previous setting.
+       pub fn sourced_from_human_readable_name($($self_mut)* $self: $self_type, hrn: HumanReadableName) -> $return_type {
+               $self.invoice_request.offer_from_hrn = Some(hrn);
+               $return_value
+       }
+
        fn build_with_checks($($self_mut)* $self: $self_type) -> Result<
                (UnsignedInvoiceRequest, Option<Keypair>, Option<&'b Secp256k1<$secp_context>>),
                Bolt12SemanticError
@@ -699,6 +709,7 @@ pub(super) struct InvoiceRequestContentsWithoutPayerSigningPubkey {
        features: InvoiceRequestFeatures,
        quantity: Option<u64>,
        payer_note: Option<String>,
+       offer_from_hrn: Option<HumanReadableName>,
        #[cfg(test)]
        experimental_bar: Option<u64>,
 }
@@ -745,6 +756,12 @@ macro_rules! invoice_request_accessors { ($self: ident, $contents: expr) => {
        pub fn payer_note(&$self) -> Option<PrintableString> {
                $contents.payer_note()
        }
+
+       /// If the [`Offer`] was sourced from a BIP 353 Human Readable Name, this should be set by the
+       /// builder to indicate the original [`HumanReadableName`] which was resolved.
+       pub fn offer_from_hrn(&$self) -> &Option<HumanReadableName> {
+               $contents.offer_from_hrn()
+       }
 } }
 
 impl UnsignedInvoiceRequest {
@@ -1004,9 +1021,7 @@ impl VerifiedInvoiceRequest {
                let InvoiceRequestContents {
                        payer_signing_pubkey,
                        inner: InvoiceRequestContentsWithoutPayerSigningPubkey {
-                               payer: _, offer: _, chain: _, amount_msats: _, features: _, quantity, payer_note,
-                               #[cfg(test)]
-                               experimental_bar: _,
+                               quantity, payer_note, ..
                        },
                } = &self.inner.contents;
 
@@ -1049,6 +1064,10 @@ impl InvoiceRequestContents {
                        .map(|payer_note| PrintableString(payer_note.as_str()))
        }
 
+       pub(super) fn offer_from_hrn(&self) -> &Option<HumanReadableName> {
+               &self.inner.offer_from_hrn
+       }
+
        pub(super) fn as_tlv_stream(&self) -> PartialInvoiceRequestTlvStreamRef {
                let (payer, offer, mut invoice_request, experimental_offer, experimental_invoice_request) =
                        self.inner.as_tlv_stream();
@@ -1085,6 +1104,7 @@ impl InvoiceRequestContentsWithoutPayerSigningPubkey {
                        quantity: self.quantity,
                        payer_id: None,
                        payer_note: self.payer_note.as_ref(),
+                       offer_from_hrn: self.offer_from_hrn.as_ref(),
                        paths: None,
                };
 
@@ -1142,6 +1162,7 @@ tlv_stream!(InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef<'a>, INVOICE_REQ
        (89, payer_note: (String, WithoutLength)),
        // Only used for Refund since the onion message of an InvoiceRequest has a reply path.
        (90, paths: (Vec<BlindedMessagePath>, WithoutLength)),
+       (91, offer_from_hrn: HumanReadableName),
 });
 
 /// Valid type range for experimental invoice_request TLV records.
@@ -1266,6 +1287,7 @@ impl TryFrom<PartialInvoiceRequestTlvStream> for InvoiceRequestContents {
                        offer_tlv_stream,
                        InvoiceRequestTlvStream {
                                chain, amount, features, quantity, payer_id, payer_note, paths,
+                               offer_from_hrn,
                        },
                        experimental_offer_tlv_stream,
                        ExperimentalInvoiceRequestTlvStream {
@@ -1305,6 +1327,7 @@ impl TryFrom<PartialInvoiceRequestTlvStream> for InvoiceRequestContents {
                Ok(InvoiceRequestContents {
                        inner: InvoiceRequestContentsWithoutPayerSigningPubkey {
                                payer, offer, chain, amount_msats: amount, features, quantity, payer_note,
+                               offer_from_hrn,
                                #[cfg(test)]
                                experimental_bar,
                        },
@@ -1484,6 +1507,7 @@ mod tests {
                                        payer_id: Some(&payer_pubkey()),
                                        payer_note: None,
                                        paths: None,
+                                       offer_from_hrn: None,
                                },
                                SignatureTlvStreamRef { signature: Some(&invoice_request.signature()) },
                                ExperimentalOfferTlvStreamRef {
index 7c9d80387de3a8f574be695533f3ea915a4bc96b..3828ecbdffc9d488d45d9397b3bb35dac2852936 100644 (file)
@@ -198,6 +198,11 @@ pub enum Bolt12SemanticError {
        InvalidSigningPubkey,
        /// A signature was expected but was missing.
        MissingSignature,
+       /// A Human Readable Name was provided but was not expected (i.e. was included in a
+       /// [`Refund`]).
+       ///
+       /// [`Refund`]: super::refund::Refund
+       UnexpectedHumanReadableName,
 }
 
 impl From<CheckedHrpstringError> for Bolt12ParseError {
index b1f5b0520ca162dd003f00e8af75953f67238d8b..7a47ad99b3ba412a7b448324999c461d13ed1abc 100644 (file)
@@ -792,6 +792,7 @@ impl RefundContents {
                        payer_id: Some(&self.payer_signing_pubkey),
                        payer_note: self.payer_note.as_ref(),
                        paths: self.paths.as_ref(),
+                       offer_from_hrn: None,
                };
 
                let experimental_offer = ExperimentalOfferTlvStreamRef {
@@ -888,7 +889,8 @@ impl TryFrom<RefundTlvStream> for RefundContents {
                                issuer_id,
                        },
                        InvoiceRequestTlvStream {
-                               chain, amount, features, quantity, payer_id, payer_note, paths
+                               chain, amount, features, quantity, payer_id, payer_note, paths,
+                               offer_from_hrn,
                        },
                        ExperimentalOfferTlvStream {
                                #[cfg(test)]
@@ -940,6 +942,11 @@ impl TryFrom<RefundTlvStream> for RefundContents {
                        return Err(Bolt12SemanticError::UnexpectedIssuerSigningPubkey);
                }
 
+               if offer_from_hrn.is_some() {
+                       // Only offers can be resolved using Human Readable Names
+                       return Err(Bolt12SemanticError::UnexpectedHumanReadableName);
+               }
+
                let amount_msats = match amount {
                        None => return Err(Bolt12SemanticError::MissingAmount),
                        Some(amount_msats) if amount_msats > MAX_VALUE_MSAT => {
@@ -1066,6 +1073,7 @@ mod tests {
                                        payer_id: Some(&payer_pubkey()),
                                        payer_note: None,
                                        paths: None,
+                                       offer_from_hrn: None,
                                },
                                ExperimentalOfferTlvStreamRef {
                                        experimental_foo: None,