diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index 6f342d4f31b..12138d1b77f 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -28,7 +28,7 @@ use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef use crate::offers::offer::{OfferTlvStream, OfferTlvStreamRef}; use crate::offers::parse::{ParseError, ParsedMessage, SemanticError}; use crate::offers::payer::{PayerTlvStream, PayerTlvStreamRef}; -use crate::offers::refund::RefundContents; +use crate::offers::refund::{Refund, RefundContents}; use crate::onion_message::BlindedPath; use crate::util::ser::{HighZeroBytesDroppedBigSize, SeekReadable, WithoutLength, Writeable, Writer}; @@ -60,6 +60,39 @@ impl<'a> InvoiceBuilder<'a> { invoice_request: &'a InvoiceRequest, paths: Vec, payinfo: Vec, created_at: Duration, payment_hash: PaymentHash ) -> Result { + let contents = InvoiceContents::ForOffer { + invoice_request: invoice_request.contents.clone(), + fields: InvoiceFields { + paths, payinfo, created_at, relative_expiry: None, payment_hash, + amount_msats: invoice_request.amount_msats(), fallbacks: None, + features: Bolt12InvoiceFeatures::empty(), + signing_pubkey: invoice_request.contents.offer.signing_pubkey(), + }, + }; + + Self::new(&invoice_request.bytes, contents) + } + + pub(super) fn for_refund( + refund: &'a Refund, paths: Vec, payinfo: Vec, + created_at: Duration, payment_hash: PaymentHash, signing_pubkey: PublicKey + ) -> Result { + let contents = InvoiceContents::ForRefund { + refund: refund.contents.clone(), + fields: InvoiceFields { + paths, payinfo, created_at, relative_expiry: None, payment_hash, + amount_msats: refund.amount_msats(), fallbacks: None, + features: Bolt12InvoiceFeatures::empty(), signing_pubkey, + }, + }; + + Self::new(&refund.bytes, contents) + } + + fn new(bytes: &'a Vec, contents: InvoiceContents) -> Result { + let paths = &contents.fields().paths; + let payinfo = &contents.fields().payinfo; + if paths.is_empty() { return Err(SemanticError::MissingPaths); } @@ -68,18 +101,7 @@ impl<'a> InvoiceBuilder<'a> { return Err(SemanticError::InvalidPayInfo); } - Ok(Self { - bytes: &invoice_request.bytes, - invoice: InvoiceContents::ForOffer { - invoice_request: invoice_request.contents.clone(), - fields: InvoiceFields { - paths, payinfo, created_at, relative_expiry: None, payment_hash, - amount_msats: invoice_request.amount_msats(), fallbacks: None, - features: Bolt12InvoiceFeatures::empty(), - signing_pubkey: invoice_request.contents.offer.signing_pubkey(), - }, - }, - }) + Ok(Self { bytes, invoice: contents }) } /// Sets the [`Invoice::relative_expiry`] as seconds since [`Invoice::created_at`]. Any expiry diff --git a/lightning/src/offers/refund.rs b/lightning/src/offers/refund.rs index 2b523fc0b0d..8c3fd182c81 100644 --- a/lightning/src/offers/refund.rs +++ b/lightning/src/offers/refund.rs @@ -77,8 +77,10 @@ use core::convert::TryFrom; use core::str::FromStr; use core::time::Duration; use crate::io; +use crate::ln::PaymentHash; use crate::ln::features::InvoiceRequestFeatures; use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT}; +use crate::offers::invoice::{BlindedPayInfo, InvoiceBuilder}; use crate::offers::invoice_request::{InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef}; use crate::offers::offer::{OfferTlvStream, OfferTlvStreamRef}; use crate::offers::parse::{Bech32Encode, ParseError, ParsedMessage, SemanticError}; @@ -200,8 +202,8 @@ impl RefundBuilder { /// [`Offer`]: crate::offers::offer::Offer #[derive(Clone, Debug)] pub struct Refund { - bytes: Vec, - contents: RefundContents, + pub(super) bytes: Vec, + pub(super) contents: RefundContents, } /// The contents of a [`Refund`], which may be shared with an `Invoice`. @@ -291,6 +293,32 @@ impl Refund { self.contents.payer_note.as_ref().map(|payer_note| PrintableString(payer_note.as_str())) } + /// Creates an [`Invoice`] for the refund with the given required fields. + /// + /// Unless [`InvoiceBuilder::relative_expiry`] is set, the invoice will expire two hours after + /// `created_at`. The caller is expected to remember the preimage of `payment_hash` in order to + /// claim a payment for the invoice. + /// + /// The `signing_pubkey` is required to sign the invoice since refunds are not in response to an + /// offer, which does have a `signing_pubkey`. + /// + /// The `paths` and `payinfo` parameters are useful for maintaining the payment recipient's + /// privacy. They must contain one or more elements and be of equal length. + /// + /// Errors if the request contains unknown required features. + /// + /// [`Invoice`]: crate::offers::invoice::Invoice + pub fn respond_with( + &self, paths: Vec, payinfo: Vec, created_at: Duration, + payment_hash: PaymentHash, signing_pubkey: PublicKey + ) -> Result { + if self.features().requires_unknown_bits() { + return Err(SemanticError::UnknownRequiredFeatures); + } + + InvoiceBuilder::for_refund(self, paths, payinfo, created_at, payment_hash, signing_pubkey) + } + #[cfg(test)] fn as_tlv_stream(&self) -> RefundTlvStreamRef { self.contents.as_tlv_stream()