//! published as a QR code to be scanned by a customer. The customer uses the offer to request an
//! invoice from the merchant to be paid.
//!
+//! # Example
+//!
//! ```
//! extern crate bitcoin;
//! extern crate core;
//! # Ok(())
//! # }
//! ```
+//!
+//! # Note
+//!
+//! If constructing an [`Offer`] for use with a [`ChannelManager`], use
+//! [`ChannelManager::create_offer_builder`] instead of [`OfferBuilder::new`].
+//!
+//! [`ChannelManager`]: crate::ln::channelmanager::ChannelManager
+//! [`ChannelManager::create_offer_builder`]: crate::ln::channelmanager::ChannelManager::create_offer_builder
use bitcoin::blockdata::constants::ChainHash;
use bitcoin::network::constants::Network;
use crate::sign::EntropySource;
use crate::io;
use crate::blinded_path::BlindedPath;
+use crate::ln::channelmanager::PaymentId;
use crate::ln::features::OfferFeatures;
use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce};
use crate::ln::msgs::MAX_VALUE_MSAT;
/// while the offer is valid.
///
/// Use a different pubkey per offer to avoid correlating offers.
+ ///
+ /// # Note
+ ///
+ /// If constructing an [`Offer`] for use with a [`ChannelManager`], use
+ /// [`ChannelManager::create_offer_builder`] instead of [`OfferBuilder::new`].
+ ///
+ /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager
+ /// [`ChannelManager::create_offer_builder`]: crate::ln::channelmanager::ChannelManager::create_offer_builder
pub fn new(description: String, signing_pubkey: PublicKey) -> Self {
OfferBuilder {
offer: OfferContents {
secp_ctx: &'a Secp256k1<T>
) -> Self where ES::Target: EntropySource {
let nonce = Nonce::from_entropy_source(entropy_source);
- let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES);
+ let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES, None);
let metadata = Metadata::DerivedSigningPubkey(derivation_material);
OfferBuilder {
offer: OfferContents {
/// See [`Offer::chains`] on how this relates to the payment currency.
///
/// Successive calls to this method will add another chain hash.
- pub fn chain(mut self, network: Network) -> Self {
+ pub fn chain(self, network: Network) -> Self {
+ self.chain_hash(ChainHash::using_genesis_block(network))
+ }
+
+ /// Adds the [`ChainHash`] to [`Offer::chains`]. If not called, the chain hash of
+ /// [`Network::Bitcoin`] is assumed to be the only one supported.
+ ///
+ /// See [`Offer::chains`] on how this relates to the payment currency.
+ ///
+ /// Successive calls to this method will add another chain hash.
+ pub(crate) fn chain_hash(mut self, chain: ChainHash) -> Self {
let chains = self.offer.chains.get_or_insert_with(Vec::new);
- let chain = ChainHash::using_genesis_block(network);
if !chains.contains(&chain) {
chains.push(chain);
}
let mut tlv_stream = self.offer.as_tlv_stream();
debug_assert_eq!(tlv_stream.metadata, None);
tlv_stream.metadata = None;
- if metadata.derives_keys() {
+ if metadata.derives_recipient_keys() {
tlv_stream.node_id = None;
}
/// The chains that may be used when paying a requested invoice (e.g., bitcoin mainnet).
/// Payments must be denominated in units of the minimal lightning-payable unit (e.g., msats)
/// for the selected chain.
- pub fn chains(&$self) -> Vec<$crate::bitcoin::blockdata::constants::ChainHash> {
+ pub fn chains(&$self) -> Vec<bitcoin::blockdata::constants::ChainHash> {
$contents.chains()
}
}
/// The public key used by the recipient to sign invoices.
- pub fn signing_pubkey(&$self) -> $crate::bitcoin::secp256k1::PublicKey {
+ pub fn signing_pubkey(&$self) -> bitcoin::secp256k1::PublicKey {
$contents.signing_pubkey()
}
} }
self.contents.is_expired()
}
+ /// Whether the offer has expired given the duration since the Unix epoch.
+ pub fn is_expired_no_std(&self, duration_since_epoch: Duration) -> bool {
+ self.contents.is_expired_no_std(duration_since_epoch)
+ }
+
/// Returns whether the given quantity is valid for the offer.
pub fn is_valid_quantity(&self, quantity: u64) -> bool {
self.contents.is_valid_quantity(quantity)
/// Similar to [`Offer::request_invoice`] except it:
/// - derives the [`InvoiceRequest::payer_id`] such that a different key can be used for each
- /// request, and
- /// - sets the [`InvoiceRequest::metadata`] when [`InvoiceRequestBuilder::build`] is called such
- /// that it can be used by [`Bolt12Invoice::verify`] to determine if the invoice was requested
- /// using a base [`ExpandedKey`] from which the payer id was derived.
+ /// request,
+ /// - sets [`InvoiceRequest::payer_metadata`] when [`InvoiceRequestBuilder::build`] is called
+ /// such that it can be used by [`Bolt12Invoice::verify`] to determine if the invoice was
+ /// requested using a base [`ExpandedKey`] from which the payer id was derived, and
+ /// - includes the [`PaymentId`] encrypted in [`InvoiceRequest::payer_metadata`] so that it can
+ /// be used when sending the payment for the requested invoice.
///
/// Useful to protect the sender's privacy.
///
/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
///
/// [`InvoiceRequest::payer_id`]: crate::offers::invoice_request::InvoiceRequest::payer_id
- /// [`InvoiceRequest::metadata`]: crate::offers::invoice_request::InvoiceRequest::metadata
+ /// [`InvoiceRequest::payer_metadata`]: crate::offers::invoice_request::InvoiceRequest::payer_metadata
/// [`Bolt12Invoice::verify`]: crate::offers::invoice::Bolt12Invoice::verify
/// [`ExpandedKey`]: crate::ln::inbound_payment::ExpandedKey
pub fn request_invoice_deriving_payer_id<'a, 'b, ES: Deref, T: secp256k1::Signing>(
- &'a self, expanded_key: &ExpandedKey, entropy_source: ES, secp_ctx: &'b Secp256k1<T>
+ &'a self, expanded_key: &ExpandedKey, entropy_source: ES, secp_ctx: &'b Secp256k1<T>,
+ payment_id: PaymentId
) -> Result<InvoiceRequestBuilder<'a, 'b, DerivedPayerId, T>, Bolt12SemanticError>
where
ES::Target: EntropySource,
return Err(Bolt12SemanticError::UnknownRequiredFeatures);
}
- Ok(InvoiceRequestBuilder::deriving_payer_id(self, expanded_key, entropy_source, secp_ctx))
+ Ok(InvoiceRequestBuilder::deriving_payer_id(
+ self, expanded_key, entropy_source, secp_ctx, payment_id
+ ))
}
/// Similar to [`Offer::request_invoice_deriving_payer_id`] except uses `payer_id` for the
///
/// [`InvoiceRequest::payer_id`]: crate::offers::invoice_request::InvoiceRequest::payer_id
pub fn request_invoice_deriving_metadata<ES: Deref>(
- &self, payer_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES
+ &self, payer_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES,
+ payment_id: PaymentId
) -> Result<InvoiceRequestBuilder<ExplicitPayerId, secp256k1::SignOnly>, Bolt12SemanticError>
where
ES::Target: EntropySource,
return Err(Bolt12SemanticError::UnknownRequiredFeatures);
}
- Ok(InvoiceRequestBuilder::deriving_metadata(self, payer_id, expanded_key, entropy_source))
+ Ok(InvoiceRequestBuilder::deriving_metadata(
+ self, payer_id, expanded_key, entropy_source, payment_id
+ ))
}
/// Creates an [`InvoiceRequestBuilder`] for the offer with the given `metadata` and `payer_id`,
#[cfg(feature = "std")]
pub(super) fn is_expired(&self) -> bool {
- match self.absolute_expiry {
- Some(seconds_from_epoch) => match SystemTime::UNIX_EPOCH.elapsed() {
- Ok(elapsed) => elapsed > seconds_from_epoch,
- Err(_) => false,
- },
- None => false,
- }
+ SystemTime::UNIX_EPOCH
+ .elapsed()
+ .map(|duration_since_epoch| self.is_expired_no_std(duration_since_epoch))
+ .unwrap_or(false)
+ }
+
+ pub(super) fn is_expired_no_std(&self, duration_since_epoch: Duration) -> bool {
+ self.absolute_expiry
+ .map(|absolute_expiry| duration_since_epoch > absolute_expiry)
+ .unwrap_or(false)
}
pub fn issuer(&self) -> Option<PrintableString> {
let tlv_stream = TlvStream::new(bytes).range(OFFER_TYPES).filter(|record| {
match record.r#type {
OFFER_METADATA_TYPE => false,
- OFFER_NODE_ID_TYPE => !self.metadata.as_ref().unwrap().derives_keys(),
+ OFFER_NODE_ID_TYPE => {
+ !self.metadata.as_ref().unwrap().derives_recipient_keys()
+ },
_ => true,
}
});
- signer::verify_metadata(
+ signer::verify_recipient_metadata(
metadata, key, IV_BYTES, self.signing_pubkey(), tlv_stream, secp_ctx
)
},
fn builds_offer_with_absolute_expiry() {
let future_expiry = Duration::from_secs(u64::max_value());
let past_expiry = Duration::from_secs(0);
+ let now = future_expiry - Duration::from_secs(1_000);
let offer = OfferBuilder::new("foo".into(), pubkey(42))
.absolute_expiry(future_expiry)
.unwrap();
#[cfg(feature = "std")]
assert!(!offer.is_expired());
+ assert!(!offer.is_expired_no_std(now));
assert_eq!(offer.absolute_expiry(), Some(future_expiry));
assert_eq!(offer.as_tlv_stream().absolute_expiry, Some(future_expiry.as_secs()));
.unwrap();
#[cfg(feature = "std")]
assert!(offer.is_expired());
+ assert!(offer.is_expired_no_std(now));
assert_eq!(offer.absolute_expiry(), Some(past_expiry));
assert_eq!(offer.as_tlv_stream().absolute_expiry, Some(past_expiry.as_secs()));
}