X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Foffers%2Finvoice.rs;h=eca4796b6702667db45d937857253e61e4d1eaeb;hb=fe83aede0c7c4e7d9adf098c07f55e52c6e38f63;hp=a937a5483c93cd48b55128b590998174807bad36;hpb=88c5197e4445166c4dd8307b9c6903ef4990c70f;p=rust-lightning diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index a937a548..eca4796b 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -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) -> 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) -> 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::()? +//! .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 { - 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 { + 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, contents: InvoiceContents) -> Result { + 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