use bitcoin::blockdata::constants::ChainHash;
use bitcoin::network::constants::Network;
use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, self};
-use core::convert::TryFrom;
+use core::hash::{Hash, Hasher};
use core::num::NonZeroU64;
use core::ops::Deref;
use core::str::FromStr;
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;
-use crate::offers::invoice_request::{DerivedPayerId, ExplicitPayerId, InvoiceRequestBuilder};
-use crate::offers::merkle::TlvStream;
+use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
+use crate::offers::merkle::{TaggedHash, TlvStream};
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::ser::{HighZeroBytesDroppedBigSize, Readable, WithoutLength, Writeable, Writer};
use crate::util::string::PrintableString;
+#[cfg(not(c_bindings))]
+use {
+ crate::offers::invoice_request::{DerivedPayerId, ExplicitPayerId, InvoiceRequestBuilder},
+};
+#[cfg(c_bindings)]
+use {
+ crate::offers::invoice_request::{InvoiceRequestWithDerivedPayerIdBuilder, InvoiceRequestWithExplicitPayerIdBuilder},
+};
+
+#[allow(unused_imports)]
use crate::prelude::*;
#[cfg(feature = "std")]
pub(super) const IV_BYTES: &[u8; IV_LEN] = b"LDK Offer ~~~~~~";
+/// An identifier for an [`Offer`] built using [`DerivedMetadata`].
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub struct OfferId(pub [u8; 32]);
+
+impl OfferId {
+ const ID_TAG: &'static str = "LDK Offer ID";
+
+ fn from_valid_offer_tlv_stream(bytes: &[u8]) -> Self {
+ let tagged_hash = TaggedHash::from_valid_tlv_stream_bytes(Self::ID_TAG, bytes);
+ Self(tagged_hash.to_bytes())
+ }
+
+ fn from_valid_invreq_tlv_stream(bytes: &[u8]) -> Self {
+ let tlv_stream = TlvStream::new(bytes).range(OFFER_TYPES);
+ let tagged_hash = TaggedHash::from_tlv_stream(Self::ID_TAG, tlv_stream);
+ Self(tagged_hash.to_bytes())
+ }
+}
+
+impl Writeable for OfferId {
+ fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
+ self.0.write(w)
+ }
+}
+
+impl Readable for OfferId {
+ fn read<R: io::Read>(r: &mut R) -> Result<Self, DecodeError> {
+ Ok(OfferId(Readable::read(r)?))
+ }
+}
+
/// Builds an [`Offer`] for the "offer to be paid" flow.
///
/// See [module-level documentation] for usage.
let mut bytes = Vec::new();
$self.offer.write(&mut bytes).unwrap();
+ let id = OfferId::from_valid_offer_tlv_stream(&bytes);
+
Offer {
bytes,
#[cfg(not(c_bindings))]
contents: $self.offer,
#[cfg(c_bindings)]
- contents: $self.offer.clone()
+ contents: $self.offer.clone(),
+ id,
}
}
} }
$return_value
}
+ #[cfg_attr(c_bindings, allow(dead_code))]
+ pub(crate) fn clear_chains($($self_mut)* $self: $self_type) -> $return_type {
+ $self.offer.chains = None;
+ $return_value
+ }
+
#[cfg_attr(c_bindings, allow(dead_code))]
pub(crate) fn clear_paths($($self_mut)* $self: $self_type) -> $return_type {
$self.offer.paths = None;
}
}
+#[cfg(c_bindings)]
+impl<'a> From<OfferWithDerivedMetadataBuilder<'a>>
+for OfferBuilder<'a, DerivedMetadata, secp256k1::All> {
+ fn from(builder: OfferWithDerivedMetadataBuilder<'a>) -> Self {
+ let OfferWithDerivedMetadataBuilder { offer, metadata_strategy, secp_ctx } = builder;
+
+ Self { offer, metadata_strategy, secp_ctx }
+ }
+}
+
/// 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
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
#[derive(Clone, Debug)]
-#[cfg_attr(test, derive(PartialEq))]
pub struct Offer {
// The serialized offer. Needed when creating an `InvoiceRequest` if the offer contains unknown
// fields.
pub(super) bytes: Vec<u8>,
pub(super) contents: OfferContents,
+ id: OfferId,
}
/// The contents of an [`Offer`], which may be shared with an [`InvoiceRequest`] or a
impl Offer {
offer_accessors!(self, self.contents);
+ /// Returns the id of the offer.
+ pub fn id(&self) -> OfferId {
+ self.id
+ }
+
pub(super) fn implied_chain(&self) -> ChainHash {
self.contents.implied_chain()
}
pub fn expects_quantity(&self) -> bool {
self.contents.expects_quantity()
}
+}
+macro_rules! request_invoice_derived_payer_id { ($self: ident, $builder: ty) => {
/// Similar to [`Offer::request_invoice`] except it:
/// - derives the [`InvoiceRequest::payer_id`] such that a different key can be used for each
/// request,
///
/// 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::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>,
+ pub fn request_invoice_deriving_payer_id<
+ 'a, 'b, ES: Deref,
+ #[cfg(not(c_bindings))]
+ T: secp256k1::Signing
+ >(
+ &'a $self, expanded_key: &ExpandedKey, entropy_source: ES,
+ #[cfg(not(c_bindings))]
+ secp_ctx: &'b Secp256k1<T>,
+ #[cfg(c_bindings)]
+ secp_ctx: &'b Secp256k1<secp256k1::All>,
payment_id: PaymentId
- ) -> Result<InvoiceRequestBuilder<'a, 'b, DerivedPayerId, T>, Bolt12SemanticError>
+ ) -> Result<$builder, Bolt12SemanticError>
where
ES::Target: EntropySource,
{
- if self.offer_features().requires_unknown_bits() {
+ if $self.offer_features().requires_unknown_bits() {
return Err(Bolt12SemanticError::UnknownRequiredFeatures);
}
- Ok(InvoiceRequestBuilder::deriving_payer_id(
- self, expanded_key, entropy_source, secp_ctx, payment_id
- ))
+ Ok(<$builder>::deriving_payer_id($self, expanded_key, entropy_source, secp_ctx, payment_id))
}
+} }
+macro_rules! request_invoice_explicit_payer_id { ($self: ident, $builder: ty) => {
/// Similar to [`Offer::request_invoice_deriving_payer_id`] except uses `payer_id` for the
/// [`InvoiceRequest::payer_id`] instead of deriving a different key for each request.
///
/// Useful for recurring payments using the same `payer_id` with different invoices.
///
- /// 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
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>
+ ) -> Result<$builder, Bolt12SemanticError>
where
ES::Target: EntropySource,
{
- if self.offer_features().requires_unknown_bits() {
+ if $self.offer_features().requires_unknown_bits() {
return Err(Bolt12SemanticError::UnknownRequiredFeatures);
}
- Ok(InvoiceRequestBuilder::deriving_metadata(
- self, payer_id, expanded_key, entropy_source, payment_id
- ))
+ Ok(<$builder>::deriving_metadata($self, payer_id, expanded_key, entropy_source, payment_id))
}
/// Creates an [`InvoiceRequestBuilder`] for the offer with the given `metadata` and `payer_id`,
///
/// Errors if the offer contains unknown required features.
///
- /// This is not exported to bindings users as builder patterns don't map outside of move semantics.
- ///
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
pub fn request_invoice(
- &self, metadata: Vec<u8>, payer_id: PublicKey
- ) -> Result<InvoiceRequestBuilder<ExplicitPayerId, secp256k1::SignOnly>, Bolt12SemanticError> {
- if self.offer_features().requires_unknown_bits() {
+ &$self, metadata: Vec<u8>, payer_id: PublicKey
+ ) -> Result<$builder, Bolt12SemanticError> {
+ if $self.offer_features().requires_unknown_bits() {
return Err(Bolt12SemanticError::UnknownRequiredFeatures);
}
- Ok(InvoiceRequestBuilder::new(self, metadata, payer_id))
+ Ok(<$builder>::new($self, metadata, payer_id))
}
+} }
- #[cfg(test)]
+#[cfg(not(c_bindings))]
+impl Offer {
+ request_invoice_derived_payer_id!(self, InvoiceRequestBuilder<'a, 'b, DerivedPayerId, T>);
+ request_invoice_explicit_payer_id!(self, InvoiceRequestBuilder<ExplicitPayerId, secp256k1::SignOnly>);
+}
+
+#[cfg(c_bindings)]
+impl Offer {
+ request_invoice_derived_payer_id!(self, InvoiceRequestWithDerivedPayerIdBuilder<'a, 'b>);
+ request_invoice_explicit_payer_id!(self, InvoiceRequestWithExplicitPayerIdBuilder);
+}
+
+#[cfg(test)]
+impl Offer {
pub(super) fn as_tlv_stream(&self) -> OfferTlvStreamRef {
self.contents.as_tlv_stream()
}
}
}
+impl PartialEq for Offer {
+ fn eq(&self, other: &Self) -> bool {
+ self.bytes.eq(&other.bytes)
+ }
+}
+
+impl Eq for Offer {}
+
+impl Hash for Offer {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.bytes.hash(state);
+ }
+}
+
impl OfferContents {
pub fn chains(&self) -> Vec<ChainHash> {
self.chains.as_ref().cloned().unwrap_or_else(|| vec![self.implied_chain()])
/// Verifies that the offer metadata was produced from the offer in the TLV stream.
pub(super) fn verify<T: secp256k1::Signing>(
&self, bytes: &[u8], key: &ExpandedKey, secp_ctx: &Secp256k1<T>
- ) -> Result<Option<KeyPair>, ()> {
+ ) -> Result<(OfferId, Option<KeyPair>), ()> {
match self.metadata() {
Some(metadata) => {
let tlv_stream = TlvStream::new(bytes).range(OFFER_TYPES).filter(|record| {
_ => true,
}
});
- signer::verify_recipient_metadata(
+ let keys = signer::verify_recipient_metadata(
metadata, key, IV_BYTES, self.signing_pubkey(), tlv_stream, secp_ctx
- )
+ )?;
+
+ let offer_id = OfferId::from_valid_invreq_tlv_stream(bytes);
+
+ Ok((offer_id, keys))
},
None => Err(()),
}
let offer = ParsedMessage::<OfferTlvStream>::try_from(bytes)?;
let ParsedMessage { bytes, tlv_stream } = offer;
let contents = OfferContents::try_from(tlv_stream)?;
- Ok(Offer { bytes, contents })
+ let id = OfferId::from_valid_offer_tlv_stream(&bytes);
+
+ Ok(Offer { bytes, contents, id })
}
}
use bitcoin::blockdata::constants::ChainHash;
use bitcoin::network::constants::Network;
use bitcoin::secp256k1::Secp256k1;
- use core::convert::TryFrom;
use core::num::NonZeroU64;
use core::time::Duration;
- use crate::blinded_path::{BlindedHop, BlindedPath};
+ use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode};
use crate::sign::KeyMaterial;
use crate::ln::features::OfferFeatures;
use crate::ln::inbound_payment::ExpandedKey;
let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap()
.sign(payer_sign).unwrap();
- assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_ok());
+ match invoice_request.verify(&expanded_key, &secp_ctx) {
+ Ok(invoice_request) => assert_eq!(invoice_request.offer_id, offer.id()),
+ Err(_) => panic!("unexpected error"),
+ }
// Fails verification with altered offer field
let mut tlv_stream = offer.as_tlv_stream();
let secp_ctx = Secp256k1::new();
let blinded_path = BlindedPath {
- introduction_node_id: pubkey(40),
+ introduction_node: IntroductionNode::NodeId(pubkey(40)),
blinding_point: pubkey(41),
blinded_hops: vec![
BlindedHop { blinded_node_id: pubkey(42), encrypted_payload: vec![0; 43] },
let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap()
.sign(payer_sign).unwrap();
- assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_ok());
+ match invoice_request.verify(&expanded_key, &secp_ctx) {
+ Ok(invoice_request) => assert_eq!(invoice_request.offer_id, offer.id()),
+ Err(_) => panic!("unexpected error"),
+ }
// Fails verification with altered offer field
let mut tlv_stream = offer.as_tlv_stream();
fn builds_offer_with_paths() {
let paths = vec![
BlindedPath {
- introduction_node_id: pubkey(40),
+ introduction_node: IntroductionNode::NodeId(pubkey(40)),
blinding_point: pubkey(41),
blinded_hops: vec![
BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] },
],
},
BlindedPath {
- introduction_node_id: pubkey(40),
+ introduction_node: IntroductionNode::NodeId(pubkey(40)),
blinding_point: pubkey(41),
blinded_hops: vec![
BlindedHop { blinded_node_id: pubkey(45), encrypted_payload: vec![0; 45] },
fn parses_offer_with_paths() {
let offer = OfferBuilder::new("foo".into(), pubkey(42))
.path(BlindedPath {
- introduction_node_id: pubkey(40),
+ introduction_node: IntroductionNode::NodeId(pubkey(40)),
blinding_point: pubkey(41),
blinded_hops: vec![
BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] },
],
})
.path(BlindedPath {
- introduction_node_id: pubkey(40),
+ introduction_node: IntroductionNode::NodeId(pubkey(40)),
blinding_point: pubkey(41),
blinded_hops: vec![
BlindedHop { blinded_node_id: pubkey(45), encrypted_payload: vec![0; 45] },