Expand invoice module docs and include an example
[rust-lightning] / lightning / src / offers / invoice.rs
index a937a5483c93cd48b55128b590998174807bad36..eca4796b6702667db45d937857253e61e4d1eaeb 100644 (file)
@@ -8,6 +8,80 @@
 // licenses.
 
 //! Data structures and encoding for `invoice` messages.
+//!
+//! An [`Invoice`] can be built from a parsed [`InvoiceRequest`] for the "offer to be paid" flow or
+//! from a [`Refund`] as an "offer for money" flow. The expected recipient of the payment then sends
+//! the invoice to the intended payer, who will then pay it.
+//!
+//! The payment recipient must include a [`PaymentHash`], so as to reveal the preimage upon payment
+//! receipt, and one or more [`BlindedPath`]s for the payer to use when sending the payment.
+//!
+//! ```ignore
+//! extern crate bitcoin;
+//! extern crate lightning;
+//!
+//! use bitcoin::hashes::Hash;
+//! use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey};
+//! use core::convert::{Infallible, TryFrom};
+//! use lightning::offers::invoice_request::InvoiceRequest;
+//! use lightning::offers::refund::Refund;
+//! use lightning::util::ser::Writeable;
+//!
+//! # use lightning::ln::PaymentHash;
+//! # use lightning::offers::invoice::BlindedPayInfo;
+//! # use lightning::onion_message::BlindedPath;
+//! #
+//! # fn create_payment_paths() -> Vec<(BlindedPath, BlindedPayInfo)> { unimplemented!() }
+//! # fn create_payment_hash() -> PaymentHash { unimplemented!() }
+//! #
+//! # fn parse_invoice_request(bytes: Vec<u8>) -> Result<(), lightning::offers::parse::ParseError> {
+//! let payment_paths = create_payment_paths();
+//! let payment_hash = create_payment_hash();
+//! let secp_ctx = Secp256k1::new();
+//! let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32])?);
+//! let pubkey = PublicKey::from(keys);
+//! let wpubkey_hash = bitcoin::util::key::PublicKey::new(pubkey).wpubkey_hash().unwrap();
+//! let mut buffer = Vec::new();
+//!
+//! // Invoice for the "offer to be paid" flow.
+//! InvoiceRequest::try_from(bytes)?
+//!     .respond_with(payment_paths, payment_hash)?
+//!     .relative_expiry(3600)
+//!     .allow_mpp()
+//!     .fallback_v0_p2wpkh(&wpubkey_hash)
+//!     .build()?
+//!     .sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys)))
+//!     .expect("failed verifying signature")
+//!     .write(&mut buffer)
+//!     .unwrap();
+//! # Ok(())
+//! # }
+//!
+//! # fn parse_refund(bytes: Vec<u8>) -> Result<(), lightning::offers::parse::ParseError> {
+//! # let payment_paths = create_payment_paths();
+//! # let payment_hash = create_payment_hash();
+//! # let secp_ctx = Secp256k1::new();
+//! # let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32])?);
+//! # let pubkey = PublicKey::from(keys);
+//! # let wpubkey_hash = bitcoin::util::key::PublicKey::new(pubkey).wpubkey_hash().unwrap();
+//! # let mut buffer = Vec::new();
+//!
+//! // Invoice for the "offer for money" flow.
+//! "lnr1qcp4256ypq"
+//!     .parse::<Refund>()?
+//!     .respond_with(payment_paths, payment_hash, pubkey)?
+//!     .relative_expiry(3600)
+//!     .allow_mpp()
+//!     .fallback_v0_p2wpkh(&wpubkey_hash)
+//!     .build()?
+//!     .sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys)))
+//!     .expect("failed verifying signature")
+//!     .write(&mut buffer)
+//!     .unwrap();
+//! # Ok(())
+//! # }
+//!
+//! ```
 
 use bitcoin::blockdata::constants::ChainHash;
 use bitcoin::hash_types::{WPubkeyHash, WScriptHash};
@@ -28,7 +102,7 @@ use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef
 use crate::offers::offer::{Amount, 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, Iterable, SeekReadable, WithoutLength, Writeable, Writer};
 
@@ -60,10 +134,6 @@ impl<'a> InvoiceBuilder<'a> {
                invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>,
                created_at: Duration, payment_hash: PaymentHash
        ) -> Result<Self, SemanticError> {
-               if payment_paths.is_empty() {
-                       return Err(SemanticError::MissingPaths);
-               }
-
                let amount_msats = match invoice_request.amount_msats() {
                        Some(amount_msats) => amount_msats,
                        None => match invoice_request.contents.offer.amount() {
@@ -75,17 +145,40 @@ impl<'a> InvoiceBuilder<'a> {
                        },
                };
 
-               Ok(Self {
-                       invreq_bytes: &invoice_request.bytes,
-                       invoice: InvoiceContents::ForOffer {
-                               invoice_request: invoice_request.contents.clone(),
-                               fields: InvoiceFields {
-                                       payment_paths, created_at, relative_expiry: None, payment_hash, amount_msats,
-                                       fallbacks: None, features: Bolt12InvoiceFeatures::empty(),
-                                       signing_pubkey: invoice_request.contents.offer.signing_pubkey(),
-                               },
+               let contents = InvoiceContents::ForOffer {
+                       invoice_request: invoice_request.contents.clone(),
+                       fields: InvoiceFields {
+                               payment_paths, created_at, relative_expiry: None, payment_hash, 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, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, created_at: Duration,
+               payment_hash: PaymentHash, signing_pubkey: PublicKey
+       ) -> Result<Self, SemanticError> {
+               let contents = InvoiceContents::ForRefund {
+                       refund: refund.contents.clone(),
+                       fields: InvoiceFields {
+                               payment_paths, 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(invreq_bytes: &'a Vec<u8>, contents: InvoiceContents) -> Result<Self, SemanticError> {
+               if contents.fields().payment_paths.is_empty() {
+                       return Err(SemanticError::MissingPaths);
+               }
+
+               Ok(Self { invreq_bytes, invoice: contents })
        }
 
        /// Sets the [`Invoice::relative_expiry`] as seconds since [`Invoice::created_at`]. Any expiry