Skip to content

Commit 3b3c0aa

Browse files
committed
Builder for creating invoices for refunds
Add a builder for creating invoices for a refund and required fields. Other settings are optional and duplicative settings will override previous settings. Building produces a semantically valid `invoice` message for the refund, which then can be signed with the key associated with the provided signing pubkey.
1 parent 0ad21f4 commit 3b3c0aa

File tree

2 files changed

+65
-15
lines changed

2 files changed

+65
-15
lines changed

lightning/src/offers/invoice.rs

+35-13
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef
2828
use crate::offers::offer::{OfferTlvStream, OfferTlvStreamRef};
2929
use crate::offers::parse::{ParseError, ParsedMessage, SemanticError};
3030
use crate::offers::payer::{PayerTlvStream, PayerTlvStreamRef};
31-
use crate::offers::refund::RefundContents;
31+
use crate::offers::refund::{Refund, RefundContents};
3232
use crate::onion_message::BlindedPath;
3333
use crate::util::ser::{HighZeroBytesDroppedBigSize, SeekReadable, WithoutLength, Writeable, Writer};
3434

@@ -60,6 +60,39 @@ impl<'a> InvoiceBuilder<'a> {
6060
invoice_request: &'a InvoiceRequest, paths: Vec<BlindedPath>, payinfo: Vec<BlindedPayInfo>,
6161
created_at: Duration, payment_hash: PaymentHash
6262
) -> Result<Self, SemanticError> {
63+
let contents = InvoiceContents::ForOffer {
64+
invoice_request: invoice_request.contents.clone(),
65+
fields: InvoiceFields {
66+
paths, payinfo, created_at, relative_expiry: None, payment_hash,
67+
amount_msats: invoice_request.amount_msats(), fallbacks: None,
68+
features: Bolt12InvoiceFeatures::empty(),
69+
signing_pubkey: invoice_request.contents.offer.signing_pubkey(),
70+
},
71+
};
72+
73+
Self::new(&invoice_request.bytes, contents)
74+
}
75+
76+
pub(super) fn for_refund(
77+
refund: &'a Refund, paths: Vec<BlindedPath>, payinfo: Vec<BlindedPayInfo>,
78+
created_at: Duration, payment_hash: PaymentHash, signing_pubkey: PublicKey
79+
) -> Result<Self, SemanticError> {
80+
let contents = InvoiceContents::ForRefund {
81+
refund: refund.contents.clone(),
82+
fields: InvoiceFields {
83+
paths, payinfo, created_at, relative_expiry: None, payment_hash,
84+
amount_msats: refund.amount_msats(), fallbacks: None,
85+
features: Bolt12InvoiceFeatures::empty(), signing_pubkey,
86+
},
87+
};
88+
89+
Self::new(&refund.bytes, contents)
90+
}
91+
92+
fn new(bytes: &'a Vec<u8>, contents: InvoiceContents) -> Result<Self, SemanticError> {
93+
let paths = &contents.fields().paths;
94+
let payinfo = &contents.fields().payinfo;
95+
6396
if paths.is_empty() {
6497
return Err(SemanticError::MissingPaths);
6598
}
@@ -68,18 +101,7 @@ impl<'a> InvoiceBuilder<'a> {
68101
return Err(SemanticError::InvalidPayInfo);
69102
}
70103

71-
Ok(Self {
72-
bytes: &invoice_request.bytes,
73-
invoice: InvoiceContents::ForOffer {
74-
invoice_request: invoice_request.contents.clone(),
75-
fields: InvoiceFields {
76-
paths, payinfo, created_at, relative_expiry: None, payment_hash,
77-
amount_msats: invoice_request.amount_msats(), fallbacks: None,
78-
features: Bolt12InvoiceFeatures::empty(),
79-
signing_pubkey: invoice_request.contents.offer.signing_pubkey(),
80-
},
81-
},
82-
})
104+
Ok(Self { bytes, invoice: contents })
83105
}
84106

85107
/// Sets the [`Invoice::relative_expiry`] as seconds since [`Invoice::created_at`]. Any expiry

lightning/src/offers/refund.rs

+30-2
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,10 @@ use core::convert::TryFrom;
7777
use core::str::FromStr;
7878
use core::time::Duration;
7979
use crate::io;
80+
use crate::ln::PaymentHash;
8081
use crate::ln::features::InvoiceRequestFeatures;
8182
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
83+
use crate::offers::invoice::{BlindedPayInfo, InvoiceBuilder};
8284
use crate::offers::invoice_request::{InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef};
8385
use crate::offers::offer::{OfferTlvStream, OfferTlvStreamRef};
8486
use crate::offers::parse::{Bech32Encode, ParseError, ParsedMessage, SemanticError};
@@ -200,8 +202,8 @@ impl RefundBuilder {
200202
/// [`Offer`]: crate::offers::offer::Offer
201203
#[derive(Clone, Debug)]
202204
pub struct Refund {
203-
bytes: Vec<u8>,
204-
contents: RefundContents,
205+
pub(super) bytes: Vec<u8>,
206+
pub(super) contents: RefundContents,
205207
}
206208

207209
/// The contents of a [`Refund`], which may be shared with an `Invoice`.
@@ -291,6 +293,32 @@ impl Refund {
291293
self.contents.payer_note.as_ref().map(|payer_note| PrintableString(payer_note.as_str()))
292294
}
293295

296+
/// Creates an [`Invoice`] for the refund with the given required fields.
297+
///
298+
/// Unless [`InvoiceBuilder::relative_expiry`] is set, the invoice will expire two hours after
299+
/// `created_at`. The caller is expected to remember the preimage of `payment_hash` in order to
300+
/// claim a payment for the invoice.
301+
///
302+
/// The `signing_pubkey` is required to sign the invoice since refunds are not in response to an
303+
/// offer, which does have a `signing_pubkey`.
304+
///
305+
/// The `paths` and `payinfo` parameters are useful for maintaining the payment recipient's
306+
/// privacy. They must contain one or more elements and be of equal length.
307+
///
308+
/// Errors if the request contains unknown required features.
309+
///
310+
/// [`Invoice`]: crate::offers::invoice::Invoice
311+
pub fn respond_with(
312+
&self, paths: Vec<BlindedPath>, payinfo: Vec<BlindedPayInfo>, created_at: Duration,
313+
payment_hash: PaymentHash, signing_pubkey: PublicKey
314+
) -> Result<InvoiceBuilder, SemanticError> {
315+
if self.features().requires_unknown_bits() {
316+
return Err(SemanticError::UnknownRequiredFeatures);
317+
}
318+
319+
InvoiceBuilder::for_refund(self, paths, payinfo, created_at, payment_hash, signing_pubkey)
320+
}
321+
294322
#[cfg(test)]
295323
fn as_tlv_stream(&self) -> RefundTlvStreamRef {
296324
self.contents.as_tlv_stream()

0 commit comments

Comments
 (0)