//!
//! use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey};
//! use lightning::offers::offer::{Offer, OfferBuilder, Quantity};
//!
//! use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey};
//! use lightning::offers::offer::{Offer, OfferBuilder, Quantity};
//! use lightning::util::ser::{Readable, Writeable};
//!
//! # use lightning::blinded_path::BlindedPath;
//! use lightning::util::ser::{Readable, Writeable};
//!
//! # use lightning::blinded_path::BlindedPath;
//! # fn create_another_blinded_path() -> BlindedPath { unimplemented!() }
//! #
//! # #[cfg(feature = "std")]
//! # fn create_another_blinded_path() -> BlindedPath { unimplemented!() }
//! #
//! # #[cfg(feature = "std")]
//! let secp_ctx = Secp256k1::new();
//! let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
//! let pubkey = PublicKey::from(keys);
//! let secp_ctx = Secp256k1::new();
//! let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
//! let pubkey = PublicKey::from(keys);
use crate::ln::msgs::MAX_VALUE_MSAT;
use crate::offers::invoice_request::{DerivedPayerId, ExplicitPayerId, InvoiceRequestBuilder};
use crate::offers::merkle::TlvStream;
use crate::ln::msgs::MAX_VALUE_MSAT;
use crate::offers::invoice_request::{DerivedPayerId, ExplicitPayerId, InvoiceRequestBuilder};
use crate::offers::merkle::TlvStream;
-use crate::offers::parse::{Bech32Encode, ParseError, ParsedMessage, SemanticError};
+use crate::offers::parse::{Bech32Encode, Bolt12ParseError, Bolt12SemanticError, ParsedMessage};
use crate::offers::signer::{Metadata, MetadataMaterial, self};
use crate::util::ser::{HighZeroBytesDroppedBigSize, WithoutLength, Writeable, Writer};
use crate::util::string::PrintableString;
use crate::offers::signer::{Metadata, MetadataMaterial, self};
use crate::util::ser::{HighZeroBytesDroppedBigSize, WithoutLength, Writeable, Writer};
use crate::util::string::PrintableString;
/// Sets the [`Offer::metadata`] to the given bytes.
///
/// Successive calls to this method will override the previous setting.
/// Sets the [`Offer::metadata`] to the given bytes.
///
/// Successive calls to this method will override the previous setting.
match self.offer.amount {
Some(Amount::Bitcoin { amount_msats }) => {
if amount_msats > MAX_VALUE_MSAT {
match self.offer.amount {
Some(Amount::Bitcoin { amount_msats }) => {
if amount_msats > MAX_VALUE_MSAT {
/// An `Offer` is a potentially long-lived proposal for payment of a good or service.
///
/// An offer is a precursor to an [`InvoiceRequest`]. A merchant publishes an offer from which a
/// An `Offer` is a potentially long-lived proposal for payment of a good or service.
///
/// An offer is a precursor to an [`InvoiceRequest`]. A merchant publishes an offer from which a
-/// customer may request an [`Invoice`] for a specific quantity and using an amount sufficient to
-/// cover that quantity (i.e., at least `quantity * amount`). See [`Offer::amount`].
+/// customer may request an [`Bolt12Invoice`] for a specific quantity and using an amount sufficient
+/// to cover that quantity (i.e., at least `quantity * amount`). See [`Offer::amount`].
/// Through the use of [`BlindedPath`]s, offers provide recipient privacy.
///
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
/// Through the use of [`BlindedPath`]s, offers provide recipient privacy.
///
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
#[derive(Clone, Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub(super) struct OfferContents {
#[derive(Clone, Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub(super) struct OfferContents {
/// - 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
/// - 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 [`Invoice::verify`] to determine if the invoice was requested using
- /// a base [`ExpandedKey`] from which the payer id was derived.
+ /// 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.
///
/// [`InvoiceRequest::payer_id`]: crate::offers::invoice_request::InvoiceRequest::payer_id
/// [`InvoiceRequest::metadata`]: crate::offers::invoice_request::InvoiceRequest::metadata
///
/// [`InvoiceRequest::payer_id`]: crate::offers::invoice_request::InvoiceRequest::payer_id
/// [`InvoiceRequest::metadata`]: crate::offers::invoice_request::InvoiceRequest::metadata
/// [`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>
/// [`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>
- ) -> Result<InvoiceRequestBuilder<'a, 'b, DerivedPayerId, T>, SemanticError>
+ ) -> Result<InvoiceRequestBuilder<'a, 'b, DerivedPayerId, T>, Bolt12SemanticError>
where
ES::Target: EntropySource,
{
if self.features().requires_unknown_bits() {
where
ES::Target: EntropySource,
{
if self.features().requires_unknown_bits() {
}
Ok(InvoiceRequestBuilder::deriving_payer_id(self, expanded_key, entropy_source, secp_ctx))
}
Ok(InvoiceRequestBuilder::deriving_payer_id(self, expanded_key, entropy_source, secp_ctx))
/// [`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
/// [`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
where
ES::Target: EntropySource,
{
if self.features().requires_unknown_bits() {
where
ES::Target: EntropySource,
{
if self.features().requires_unknown_bits() {
}
Ok(InvoiceRequestBuilder::deriving_metadata(self, payer_id, expanded_key, entropy_source))
}
/// Creates an [`InvoiceRequestBuilder`] for the offer with the given `metadata` and `payer_id`,
}
Ok(InvoiceRequestBuilder::deriving_metadata(self, payer_id, expanded_key, entropy_source))
}
/// Creates an [`InvoiceRequestBuilder`] for the offer with the given `metadata` and `payer_id`,
///
/// The `metadata` is useful for including information about the derivation of `payer_id` such
/// that invoice response handling can be stateless. Also serves as payer-provided entropy while
///
/// The `metadata` is useful for including information about the derivation of `payer_id` such
/// that invoice response handling can be stateless. Also serves as payer-provided entropy while
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
pub fn request_invoice(
&self, metadata: Vec<u8>, payer_id: PublicKey
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
pub fn request_invoice(
&self, metadata: Vec<u8>, payer_id: PublicKey
pub(super) fn check_amount_msats_for_quantity(
&self, amount_msats: Option<u64>, quantity: Option<u64>
pub(super) fn check_amount_msats_for_quantity(
&self, amount_msats: Option<u64>, quantity: Option<u64>
let offer_amount_msats = match self.amount {
None => 0,
Some(Amount::Bitcoin { amount_msats }) => amount_msats,
let offer_amount_msats = match self.amount {
None => 0,
Some(Amount::Bitcoin { amount_msats }) => amount_msats,
};
if !self.expects_quantity() || quantity.is_some() {
let expected_amount_msats = offer_amount_msats.checked_mul(quantity.unwrap_or(1))
};
if !self.expects_quantity() || quantity.is_some() {
let expected_amount_msats = offer_amount_msats.checked_mul(quantity.unwrap_or(1))
let amount_msats = amount_msats.unwrap_or(expected_amount_msats);
if amount_msats < expected_amount_msats {
let amount_msats = amount_msats.unwrap_or(expected_amount_msats);
if amount_msats < expected_amount_msats {
- None if expects_quantity => Err(SemanticError::MissingQuantity),
- Some(_) if !expects_quantity => Err(SemanticError::UnexpectedQuantity),
+ None if expects_quantity => Err(Bolt12SemanticError::MissingQuantity),
+ Some(_) if !expects_quantity => Err(Bolt12SemanticError::UnexpectedQuantity),
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
let offer = ParsedMessage::<OfferTlvStream>::try_from(bytes)?;
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
let offer = ParsedMessage::<OfferTlvStream>::try_from(bytes)?;
let amount = match (currency, amount) {
(None, None) => None,
(None, Some(amount_msats)) if amount_msats > MAX_VALUE_MSAT => {
let amount = match (currency, amount) {
(None, None) => None,
(None, Some(amount_msats)) if amount_msats > MAX_VALUE_MSAT => {
(Some(iso4217_code), Some(amount)) => Some(Amount::Currency { iso4217_code, amount }),
};
let description = match description {
(Some(iso4217_code), Some(amount)) => Some(Amount::Currency { iso4217_code, amount }),
};
let description = match description {
use crate::ln::features::OfferFeatures;
use crate::ln::inbound_payment::ExpandedKey;
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
use crate::ln::features::OfferFeatures;
use crate::ln::inbound_payment::ExpandedKey;
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
use crate::offers::test_utils::*;
use crate::util::ser::{BigSize, Writeable};
use crate::util::string::PrintableString;
use crate::offers::test_utils::*;
use crate::util::ser::{BigSize, Writeable};
use crate::util::string::PrintableString;
assert_eq!(tlv_stream.currency, Some(b"USD"));
match builder.build() {
Ok(_) => panic!("expected error"),
assert_eq!(tlv_stream.currency, Some(b"USD"));
match builder.build() {
Ok(_) => panic!("expected error"),
let invalid_amount = Amount::Bitcoin { amount_msats: MAX_VALUE_MSAT + 1 };
match OfferBuilder::new("foo".into(), pubkey(42)).amount(invalid_amount).build() {
Ok(_) => panic!("expected error"),
let invalid_amount = Amount::Bitcoin { amount_msats: MAX_VALUE_MSAT + 1 };
match OfferBuilder::new("foo".into(), pubkey(42)).amount(invalid_amount).build() {
Ok(_) => panic!("expected error"),
.request_invoice(vec![1; 32], pubkey(43))
{
Ok(_) => panic!("expected error"),
.request_invoice(vec![1; 32], pubkey(43))
{
Ok(_) => panic!("expected error"),
match Offer::try_from(encoded_offer) {
Ok(_) => panic!("expected error"),
match Offer::try_from(encoded_offer) {
Ok(_) => panic!("expected error"),
match Offer::try_from(encoded_offer) {
Ok(_) => panic!("expected error"),
match Offer::try_from(encoded_offer) {
Ok(_) => panic!("expected error"),
match Offer::try_from(encoded_offer) {
Ok(_) => panic!("expected error"),
Err(e) => {
match Offer::try_from(encoded_offer) {
Ok(_) => panic!("expected error"),
Err(e) => {
match Offer::try_from(encoded_offer) {
Ok(_) => panic!("expected error"),
Err(e) => {
match Offer::try_from(encoded_offer) {
Ok(_) => panic!("expected error"),
Err(e) => {
match Offer::try_from(encoded_offer) {
Ok(_) => panic!("expected error"),
match Offer::try_from(encoded_offer) {
Ok(_) => panic!("expected error"),
for encoded_offer in &offers {
match encoded_offer.parse::<Offer>() {
Ok(_) => panic!("Valid offer: {}", encoded_offer),
for encoded_offer in &offers {
match encoded_offer.parse::<Offer>() {
Ok(_) => panic!("Valid offer: {}", encoded_offer),
let encoded_offer = "lni1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg";
match encoded_offer.parse::<Offer>() {
Ok(_) => panic!("Valid offer: {}", encoded_offer),
let encoded_offer = "lni1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg";
match encoded_offer.parse::<Offer>() {
Ok(_) => panic!("Valid offer: {}", encoded_offer),
let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxo";
match encoded_offer.parse::<Offer>() {
Ok(_) => panic!("Valid offer: {}", encoded_offer),
let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxo";
match encoded_offer.parse::<Offer>() {
Ok(_) => panic!("Valid offer: {}", encoded_offer),
let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxgqqqqq";
match encoded_offer.parse::<Offer>() {
Ok(_) => panic!("Valid offer: {}", encoded_offer),
let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxgqqqqq";
match encoded_offer.parse::<Offer>() {
Ok(_) => panic!("Valid offer: {}", encoded_offer),