Skip to content

Commit

Permalink
Builder for creating invoices for refunds
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jkczyz committed Dec 22, 2022
1 parent 2209926 commit b409b29
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 15 deletions.
48 changes: 35 additions & 13 deletions lightning/src/offers/invoice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -60,6 +60,39 @@ impl<'a> InvoiceBuilder<'a> {
invoice_request: &'a InvoiceRequest, paths: Vec<BlindedPath>, payinfo: Vec<BlindedPayInfo>,
created_at: Duration, payment_hash: PaymentHash
) -> Result<Self, SemanticError> {
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<BlindedPath>, payinfo: Vec<BlindedPayInfo>,
created_at: Duration, payment_hash: PaymentHash, signing_pubkey: PublicKey
) -> Result<Self, SemanticError> {
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<u8>, contents: InvoiceContents) -> Result<Self, SemanticError> {
let paths = &contents.fields().paths;
let payinfo = &contents.fields().payinfo;

if paths.is_empty() {
return Err(SemanticError::MissingPaths);
}
Expand All @@ -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
Expand Down
32 changes: 30 additions & 2 deletions lightning/src/offers/refund.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -200,8 +202,8 @@ impl RefundBuilder {
/// [`Offer`]: crate::offers::offer::Offer
#[derive(Clone, Debug)]
pub struct Refund {
bytes: Vec<u8>,
contents: RefundContents,
pub(super) bytes: Vec<u8>,
pub(super) contents: RefundContents,
}

/// The contents of a [`Refund`], which may be shared with an `Invoice`.
Expand Down Expand Up @@ -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<BlindedPath>, payinfo: Vec<BlindedPayInfo>, created_at: Duration,
payment_hash: PaymentHash, signing_pubkey: PublicKey
) -> Result<InvoiceBuilder, SemanticError> {
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()
Expand Down

0 comments on commit b409b29

Please sign in to comment.