From a517925f6c4bda897d226a31be1a45bef173def9 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Mon, 19 Feb 2024 18:43:59 -0600 Subject: [PATCH 01/13] Macro-ize OfferBuilder OfferBuilder is not exported to bindings because it has methods that take `self` by value and are only implemented for certain type parameterizations. Define these methods using macros such that different builders and related methods can be defined for c_bindings. --- lightning/src/offers/offer.rs | 136 ++++++++++++++++++++-------------- 1 file changed, 79 insertions(+), 57 deletions(-) diff --git a/lightning/src/offers/offer.rs b/lightning/src/offers/offer.rs index 802813fd180..2033efee623 100644 --- a/lightning/src/offers/offer.rs +++ b/lightning/src/offers/offer.rs @@ -134,9 +134,12 @@ pub struct ExplicitMetadata {} pub struct DerivedMetadata {} impl MetadataStrategy for ExplicitMetadata {} + impl MetadataStrategy for DerivedMetadata {} -impl<'a> OfferBuilder<'a, ExplicitMetadata, secp256k1::SignOnly> { +macro_rules! offer_explicit_metadata_builder_methods { ( + $self: ident, $self_type: ty, $return_type: ty, $return_value: expr +) => { /// Creates a new builder for an offer setting the [`Offer::description`] and using the /// [`Offer::signing_pubkey`] for signing invoices. The associated secret key must be remembered /// while the offer is valid. @@ -151,7 +154,7 @@ impl<'a> OfferBuilder<'a, ExplicitMetadata, secp256k1::SignOnly> { /// [`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 { + Self { offer: OfferContents { chains: None, metadata: None, amount: None, description, features: OfferFeatures::empty(), absolute_expiry: None, issuer: None, paths: None, @@ -165,13 +168,13 @@ impl<'a> OfferBuilder<'a, ExplicitMetadata, secp256k1::SignOnly> { /// Sets the [`Offer::metadata`] to the given bytes. /// /// Successive calls to this method will override the previous setting. - pub fn metadata(mut self, metadata: Vec) -> Result { - self.offer.metadata = Some(Metadata::Bytes(metadata)); - Ok(self) + pub fn metadata(mut $self: $self_type, metadata: Vec) -> Result<$return_type, Bolt12SemanticError> { + $self.offer.metadata = Some(Metadata::Bytes(metadata)); + Ok($return_value) } -} +} } -impl<'a, T: secp256k1::Signing> OfferBuilder<'a, DerivedMetadata, T> { +macro_rules! offer_derived_metadata_builder_methods { () => { /// Similar to [`OfferBuilder::new`] except, if [`OfferBuilder::path`] is called, the signing /// pubkey is derived from the given [`ExpandedKey`] and [`EntropySource`]. This provides /// recipient privacy by using a different signing pubkey for each offer. Otherwise, the @@ -190,7 +193,7 @@ impl<'a, T: secp256k1::Signing> OfferBuilder<'a, DerivedMetadata, T> { let nonce = Nonce::from_entropy_source(entropy_source); let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES, None); let metadata = Metadata::DerivedSigningPubkey(derivation_material); - OfferBuilder { + Self { offer: OfferContents { chains: None, metadata: Some(metadata), amount: None, description, features: OfferFeatures::empty(), absolute_expiry: None, issuer: None, paths: None, @@ -200,17 +203,19 @@ impl<'a, T: secp256k1::Signing> OfferBuilder<'a, DerivedMetadata, T> { secp_ctx: Some(secp_ctx), } } -} +} } -impl<'a, M: MetadataStrategy, T: secp256k1::Signing> OfferBuilder<'a, M, T> { +macro_rules! offer_builder_methods { ( + $self: ident, $self_type: ty, $return_type: ty, $return_value: expr +) => { /// Adds the chain hash of the given [`Network`] 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 fn chain(self, network: Network) -> Self { - self.chain_hash(ChainHash::using_genesis_block(network)) + pub fn chain($self: $self_type, network: Network) -> $return_type { + $self.chain_hash(ChainHash::using_genesis_block(network)) } /// Adds the [`ChainHash`] to [`Offer::chains`]. If not called, the chain hash of @@ -219,45 +224,45 @@ impl<'a, M: MetadataStrategy, T: secp256k1::Signing> OfferBuilder<'a, M, T> { /// 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); + pub(crate) fn chain_hash(mut $self: $self_type, chain: ChainHash) -> $return_type { + let chains = $self.offer.chains.get_or_insert_with(Vec::new); if !chains.contains(&chain) { chains.push(chain); } - self + $return_value } /// Sets the [`Offer::amount`] as an [`Amount::Bitcoin`]. /// /// Successive calls to this method will override the previous setting. - pub fn amount_msats(self, amount_msats: u64) -> Self { - self.amount(Amount::Bitcoin { amount_msats }) + pub fn amount_msats($self: $self_type, amount_msats: u64) -> $return_type { + $self.amount(Amount::Bitcoin { amount_msats }) } /// Sets the [`Offer::amount`]. /// /// Successive calls to this method will override the previous setting. - pub(super) fn amount(mut self, amount: Amount) -> Self { - self.offer.amount = Some(amount); - self + pub(super) fn amount(mut $self: $self_type, amount: Amount) -> $return_type { + $self.offer.amount = Some(amount); + $return_value } /// Sets the [`Offer::absolute_expiry`] as seconds since the Unix epoch. Any expiry that has /// already passed is valid and can be checked for using [`Offer::is_expired`]. /// /// Successive calls to this method will override the previous setting. - pub fn absolute_expiry(mut self, absolute_expiry: Duration) -> Self { - self.offer.absolute_expiry = Some(absolute_expiry); - self + pub fn absolute_expiry(mut $self: $self_type, absolute_expiry: Duration) -> $return_type { + $self.offer.absolute_expiry = Some(absolute_expiry); + $return_value } /// Sets the [`Offer::issuer`]. /// /// Successive calls to this method will override the previous setting. - pub fn issuer(mut self, issuer: String) -> Self { - self.offer.issuer = Some(issuer); - self + pub fn issuer(mut $self: $self_type, issuer: String) -> $return_type { + $self.offer.issuer = Some(issuer); + $return_value } /// Adds a blinded path to [`Offer::paths`]. Must include at least one path if only connected by @@ -265,23 +270,23 @@ impl<'a, M: MetadataStrategy, T: secp256k1::Signing> OfferBuilder<'a, M, T> { /// /// Successive calls to this method will add another blinded path. Caller is responsible for not /// adding duplicate paths. - pub fn path(mut self, path: BlindedPath) -> Self { - self.offer.paths.get_or_insert_with(Vec::new).push(path); - self + pub fn path(mut $self: $self_type, path: BlindedPath) -> $return_type { + $self.offer.paths.get_or_insert_with(Vec::new).push(path); + $return_value } /// Sets the quantity of items for [`Offer::supported_quantity`]. If not called, defaults to /// [`Quantity::One`]. /// /// Successive calls to this method will override the previous setting. - pub fn supported_quantity(mut self, quantity: Quantity) -> Self { - self.offer.supported_quantity = quantity; - self + pub fn supported_quantity(mut $self: $self_type, quantity: Quantity) -> $return_type { + $self.offer.supported_quantity = quantity; + $return_value } /// Builds an [`Offer`] from the builder's settings. - pub fn build(mut self) -> Result { - match self.offer.amount { + pub fn build(mut $self: $self_type) -> Result { + match $self.offer.amount { Some(Amount::Bitcoin { amount_msats }) => { if amount_msats > MAX_VALUE_MSAT { return Err(Bolt12SemanticError::InvalidAmount); @@ -291,62 +296,79 @@ impl<'a, M: MetadataStrategy, T: secp256k1::Signing> OfferBuilder<'a, M, T> { None => {}, } - if let Some(chains) = &self.offer.chains { - if chains.len() == 1 && chains[0] == self.offer.implied_chain() { - self.offer.chains = None; + if let Some(chains) = &$self.offer.chains { + if chains.len() == 1 && chains[0] == $self.offer.implied_chain() { + $self.offer.chains = None; } } - Ok(self.build_without_checks()) + Ok($self.build_without_checks()) } - fn build_without_checks(mut self) -> Offer { + fn build_without_checks(mut $self: $self_type) -> Offer { // Create the metadata for stateless verification of an InvoiceRequest. - if let Some(mut metadata) = self.offer.metadata.take() { + if let Some(mut metadata) = $self.offer.metadata.take() { if metadata.has_derivation_material() { - if self.offer.paths.is_none() { + if $self.offer.paths.is_none() { metadata = metadata.without_keys(); } - let mut tlv_stream = self.offer.as_tlv_stream(); + let mut tlv_stream = $self.offer.as_tlv_stream(); debug_assert_eq!(tlv_stream.metadata, None); tlv_stream.metadata = None; if metadata.derives_recipient_keys() { tlv_stream.node_id = None; } - let (derived_metadata, keys) = metadata.derive_from(tlv_stream, self.secp_ctx); + let (derived_metadata, keys) = metadata.derive_from(tlv_stream, $self.secp_ctx); metadata = derived_metadata; if let Some(keys) = keys { - self.offer.signing_pubkey = keys.public_key(); + $self.offer.signing_pubkey = keys.public_key(); } } - self.offer.metadata = Some(metadata); + $self.offer.metadata = Some(metadata); } let mut bytes = Vec::new(); - self.offer.write(&mut bytes).unwrap(); + $self.offer.write(&mut bytes).unwrap(); - Offer { bytes, contents: self.offer } + Offer { bytes, contents: $self.offer } } -} +} } #[cfg(test)] -impl<'a, M: MetadataStrategy, T: secp256k1::Signing> OfferBuilder<'a, M, T> { - fn features_unchecked(mut self, features: OfferFeatures) -> Self { - self.offer.features = features; - self +macro_rules! offer_builder_test_methods { ( + $self: ident, $self_type: ty, $return_type: ty, $return_value: expr +) => { + fn features_unchecked(mut $self: $self_type, features: OfferFeatures) -> $return_type { + $self.offer.features = features; + $return_value } - pub(crate) fn clear_paths(mut self) -> Self { - self.offer.paths = None; - self + pub(crate) fn clear_paths(mut $self: $self_type) -> $return_type { + $self.offer.paths = None; + $return_value } - pub(super) fn build_unchecked(self) -> Offer { - self.build_without_checks() + pub(super) fn build_unchecked($self: $self_type) -> Offer { + $self.build_without_checks() } +} } + +impl<'a, M: MetadataStrategy, T: secp256k1::Signing> OfferBuilder<'a, M, T> { + offer_builder_methods!(self, Self, Self, self); + + #[cfg(test)] + offer_builder_test_methods!(self, Self, Self, self); +} + +impl<'a> OfferBuilder<'a, ExplicitMetadata, secp256k1::SignOnly> { + offer_explicit_metadata_builder_methods!(self, Self, Self, self); +} + +impl<'a, T: secp256k1::Signing> OfferBuilder<'a, DerivedMetadata, T> { + offer_derived_metadata_builder_methods!(); } /// An `Offer` is a potentially long-lived proposal for payment of a good or service. From d9ab2fa58177fc390891e0229e9db269e8d54f6e Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Fri, 23 Feb 2024 16:33:23 -0600 Subject: [PATCH 02/13] Add c_bindings version of OfferBuilder Use the macros introduced in the previous commit to define two builders for each type parameterization of OfferBuilder - OfferWithExplicitMetadataBuilder - OfferWithDerivedMetadataBuilder The difference between these and OfferBuilder is that these have methods that take `self` by mutable reference instead of by value and don't return anything instead returning the modified builder. This is required because bindings don't support move semantics nor impl blocks specific to a certain type parameterization. Because of this, the builder's contents must be cloned when building an Offer. Keeps OfferBuilder defined so that it can be used internally in ChannelManager::create_offer_builder even when compiled for c_bindings. --- lightning/src/ln/channelmanager.rs | 47 +++++++-- lightning/src/offers/invoice.rs | 12 ++- lightning/src/offers/invoice_request.rs | 10 +- lightning/src/offers/offer.rs | 128 ++++++++++++++++++++---- 4 files changed, 165 insertions(+), 32 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index ec3e8ef604b..d41909beb3a 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -60,7 +60,7 @@ use crate::ln::wire::Encode; use crate::offers::invoice::{BlindedPayInfo, Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, DerivedSigningPubkey, InvoiceBuilder}; use crate::offers::invoice_error::InvoiceError; use crate::offers::merkle::SignError; -use crate::offers::offer::{DerivedMetadata, Offer, OfferBuilder}; +use crate::offers::offer::{Offer, OfferBuilder}; use crate::offers::parse::Bolt12SemanticError; use crate::offers::refund::{Refund, RefundBuilder}; use crate::onion_message::messenger::{Destination, MessageRouter, PendingOnionMessage, new_pending_onion_message}; @@ -76,11 +76,16 @@ use crate::util::logger::{Level, Logger, WithContext}; use crate::util::errors::APIError; #[cfg(not(c_bindings))] use { + crate::offers::offer::DerivedMetadata, crate::routing::router::DefaultRouter, crate::routing::gossip::NetworkGraph, crate::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringFeeParameters}, crate::sign::KeysManager, }; +#[cfg(c_bindings)] +use { + crate::offers::offer::OfferWithDerivedMetadataBuilder, +}; use alloc::collections::{btree_map, BTreeMap}; @@ -7520,7 +7525,9 @@ where self.finish_close_channel(failure); } } +} +macro_rules! create_offer_builder { ($self: ident, $builder: ty) => { /// Creates an [`OfferBuilder`] such that the [`Offer`] it builds is recognized by the /// [`ChannelManager`] when handling [`InvoiceRequest`] messages for the offer. The offer will /// not have an expiration unless otherwise set on the builder. @@ -7549,22 +7556,40 @@ where /// [`Offer`]: crate::offers::offer::Offer /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest pub fn create_offer_builder( - &self, description: String - ) -> Result, Bolt12SemanticError> { - let node_id = self.get_our_node_id(); - let expanded_key = &self.inbound_payment_key; - let entropy = &*self.entropy_source; - let secp_ctx = &self.secp_ctx; - - let path = self.create_blinded_path().map_err(|_| Bolt12SemanticError::MissingPaths)?; + &$self, description: String + ) -> Result<$builder, Bolt12SemanticError> { + let node_id = $self.get_our_node_id(); + let expanded_key = &$self.inbound_payment_key; + let entropy = &*$self.entropy_source; + let secp_ctx = &$self.secp_ctx; + + let path = $self.create_blinded_path().map_err(|_| Bolt12SemanticError::MissingPaths)?; let builder = OfferBuilder::deriving_signing_pubkey( description, node_id, expanded_key, entropy, secp_ctx ) - .chain_hash(self.chain_hash) + .chain_hash($self.chain_hash) .path(path); - Ok(builder) + Ok(builder.into()) } +} } + +impl ChannelManager +where + M::Target: chain::Watch<::EcdsaSigner>, + T::Target: BroadcasterInterface, + ES::Target: EntropySource, + NS::Target: NodeSigner, + SP::Target: SignerProvider, + F::Target: FeeEstimator, + R::Target: Router, + L::Target: Logger, +{ + #[cfg(not(c_bindings))] + create_offer_builder!(self, OfferBuilder); + + #[cfg(c_bindings)] + create_offer_builder!(self, OfferWithDerivedMetadataBuilder); /// Creates a [`RefundBuilder`] such that the [`Refund`] it builds is recognized by the /// [`ChannelManager`] when handling [`Bolt12Invoice`] messages for the refund. diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index bb29c76164e..ebb23fb2ee0 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -1306,7 +1306,15 @@ mod tests { use crate::ln::msgs::DecodeError; use crate::offers::invoice_request::InvoiceRequestTlvStreamRef; use crate::offers::merkle::{SignError, SignatureTlvStreamRef, TaggedHash, self}; - use crate::offers::offer::{Amount, OfferBuilder, OfferTlvStreamRef, Quantity}; + use crate::offers::offer::{Amount, OfferTlvStreamRef, Quantity}; + #[cfg(not(c_bindings))] + use { + crate::offers::offer::OfferBuilder, + }; + #[cfg(c_bindings)] + use { + crate::offers::offer::OfferWithExplicitMetadataBuilder as OfferBuilder, + }; use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError}; use crate::offers::payer::PayerTlvStreamRef; use crate::offers::refund::RefundBuilder; @@ -1644,6 +1652,8 @@ mod tests { ], }; + #[cfg(c_bindings)] + use crate::offers::offer::OfferWithDerivedMetadataBuilder as OfferBuilder; let offer = OfferBuilder ::deriving_signing_pubkey(desc, node_id, &expanded_key, &entropy, &secp_ctx) .amount_msats(1000) diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index 4dd85b352f7..ecdde2b6657 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -941,7 +941,15 @@ mod tests { use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT}; use crate::offers::invoice::{Bolt12Invoice, SIGNATURE_TAG as INVOICE_SIGNATURE_TAG}; use crate::offers::merkle::{SignError, SignatureTlvStreamRef, TaggedHash, self}; - use crate::offers::offer::{Amount, OfferBuilder, OfferTlvStreamRef, Quantity}; + use crate::offers::offer::{Amount, OfferTlvStreamRef, Quantity}; + #[cfg(not(c_bindings))] + use { + crate::offers::offer::OfferBuilder, + }; + #[cfg(c_bindings)] + use { + crate::offers::offer::OfferWithExplicitMetadataBuilder as OfferBuilder, + }; use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError}; use crate::offers::payer::PayerTlvStreamRef; use crate::offers::test_utils::*; diff --git a/lightning/src/offers/offer.rs b/lightning/src/offers/offer.rs index 2033efee623..5b5b66e32b6 100644 --- a/lightning/src/offers/offer.rs +++ b/lightning/src/offers/offer.rs @@ -118,6 +118,34 @@ pub struct OfferBuilder<'a, M: MetadataStrategy, T: secp256k1::Signing> { secp_ctx: Option<&'a Secp256k1>, } +/// Builds an [`Offer`] for the "offer to be paid" flow. +/// +/// See [module-level documentation] for usage. +/// +/// This is not exported to bindings users as builder patterns don't map outside of move semantics. +/// +/// [module-level documentation]: self +#[cfg(c_bindings)] +pub struct OfferWithExplicitMetadataBuilder<'a> { + offer: OfferContents, + metadata_strategy: core::marker::PhantomData, + secp_ctx: Option<&'a Secp256k1>, +} + +/// Builds an [`Offer`] for the "offer to be paid" flow. +/// +/// See [module-level documentation] for usage. +/// +/// This is not exported to bindings users as builder patterns don't map outside of move semantics. +/// +/// [module-level documentation]: self +#[cfg(c_bindings)] +pub struct OfferWithDerivedMetadataBuilder<'a> { + offer: OfferContents, + metadata_strategy: core::marker::PhantomData, + secp_ctx: Option<&'a Secp256k1>, +} + /// Indicates how [`Offer::metadata`] may be set. /// /// This is not exported to bindings users as builder patterns don't map outside of move semantics. @@ -174,7 +202,7 @@ macro_rules! offer_explicit_metadata_builder_methods { ( } } } -macro_rules! offer_derived_metadata_builder_methods { () => { +macro_rules! offer_derived_metadata_builder_methods { ($secp_context: ty) => { /// Similar to [`OfferBuilder::new`] except, if [`OfferBuilder::path`] is called, the signing /// pubkey is derived from the given [`ExpandedKey`] and [`EntropySource`]. This provides /// recipient privacy by using a different signing pubkey for each offer. Otherwise, the @@ -188,7 +216,7 @@ macro_rules! offer_derived_metadata_builder_methods { () => { /// [`ExpandedKey`]: crate::ln::inbound_payment::ExpandedKey pub fn deriving_signing_pubkey( description: String, node_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES, - secp_ctx: &'a Secp256k1 + secp_ctx: &'a Secp256k1<$secp_context> ) -> Self where ES::Target: EntropySource { let nonce = Nonce::from_entropy_source(entropy_source); let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES, None); @@ -206,7 +234,7 @@ macro_rules! offer_derived_metadata_builder_methods { () => { } } macro_rules! offer_builder_methods { ( - $self: ident, $self_type: ty, $return_type: ty, $return_value: expr + $self: ident, $self_type: ty, $return_type: ty, $return_value: expr $(, $self_mut: tt)? ) => { /// Adds the chain hash of the given [`Network`] to [`Offer::chains`]. If not called, /// the chain hash of [`Network::Bitcoin`] is assumed to be the only one supported. @@ -224,7 +252,7 @@ macro_rules! offer_builder_methods { ( /// 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: $self_type, chain: ChainHash) -> $return_type { + pub(crate) fn chain_hash($($self_mut)* $self: $self_type, chain: ChainHash) -> $return_type { let chains = $self.offer.chains.get_or_insert_with(Vec::new); if !chains.contains(&chain) { chains.push(chain); @@ -243,7 +271,7 @@ macro_rules! offer_builder_methods { ( /// Sets the [`Offer::amount`]. /// /// Successive calls to this method will override the previous setting. - pub(super) fn amount(mut $self: $self_type, amount: Amount) -> $return_type { + pub(super) fn amount($($self_mut)* $self: $self_type, amount: Amount) -> $return_type { $self.offer.amount = Some(amount); $return_value } @@ -252,7 +280,7 @@ macro_rules! offer_builder_methods { ( /// already passed is valid and can be checked for using [`Offer::is_expired`]. /// /// Successive calls to this method will override the previous setting. - pub fn absolute_expiry(mut $self: $self_type, absolute_expiry: Duration) -> $return_type { + pub fn absolute_expiry($($self_mut)* $self: $self_type, absolute_expiry: Duration) -> $return_type { $self.offer.absolute_expiry = Some(absolute_expiry); $return_value } @@ -260,7 +288,7 @@ macro_rules! offer_builder_methods { ( /// Sets the [`Offer::issuer`]. /// /// Successive calls to this method will override the previous setting. - pub fn issuer(mut $self: $self_type, issuer: String) -> $return_type { + pub fn issuer($($self_mut)* $self: $self_type, issuer: String) -> $return_type { $self.offer.issuer = Some(issuer); $return_value } @@ -270,7 +298,7 @@ macro_rules! offer_builder_methods { ( /// /// Successive calls to this method will add another blinded path. Caller is responsible for not /// adding duplicate paths. - pub fn path(mut $self: $self_type, path: BlindedPath) -> $return_type { + pub fn path($($self_mut)* $self: $self_type, path: BlindedPath) -> $return_type { $self.offer.paths.get_or_insert_with(Vec::new).push(path); $return_value } @@ -279,13 +307,13 @@ macro_rules! offer_builder_methods { ( /// [`Quantity::One`]. /// /// Successive calls to this method will override the previous setting. - pub fn supported_quantity(mut $self: $self_type, quantity: Quantity) -> $return_type { + pub fn supported_quantity($($self_mut)* $self: $self_type, quantity: Quantity) -> $return_type { $self.offer.supported_quantity = quantity; $return_value } /// Builds an [`Offer`] from the builder's settings. - pub fn build(mut $self: $self_type) -> Result { + pub fn build($($self_mut)* $self: $self_type) -> Result { match $self.offer.amount { Some(Amount::Bitcoin { amount_msats }) => { if amount_msats > MAX_VALUE_MSAT { @@ -305,7 +333,7 @@ macro_rules! offer_builder_methods { ( Ok($self.build_without_checks()) } - fn build_without_checks(mut $self: $self_type) -> Offer { + fn build_without_checks($($self_mut)* $self: $self_type) -> Offer { // Create the metadata for stateless verification of an InvoiceRequest. if let Some(mut metadata) = $self.offer.metadata.take() { if metadata.has_derivation_material() { @@ -333,34 +361,43 @@ macro_rules! offer_builder_methods { ( let mut bytes = Vec::new(); $self.offer.write(&mut bytes).unwrap(); - Offer { bytes, contents: $self.offer } + Offer { + bytes, + #[cfg(not(c_bindings))] + contents: $self.offer, + #[cfg(c_bindings)] + contents: $self.offer.clone() + } } } } #[cfg(test)] macro_rules! offer_builder_test_methods { ( - $self: ident, $self_type: ty, $return_type: ty, $return_value: expr + $self: ident, $self_type: ty, $return_type: ty, $return_value: expr $(, $self_mut: tt)? ) => { - fn features_unchecked(mut $self: $self_type, features: OfferFeatures) -> $return_type { + #[cfg_attr(c_bindings, allow(dead_code))] + fn features_unchecked($($self_mut)* $self: $self_type, features: OfferFeatures) -> $return_type { $self.offer.features = features; $return_value } - pub(crate) fn clear_paths(mut $self: $self_type) -> $return_type { + #[cfg_attr(c_bindings, allow(dead_code))] + pub(crate) fn clear_paths($($self_mut)* $self: $self_type) -> $return_type { $self.offer.paths = None; $return_value } + #[cfg_attr(c_bindings, allow(dead_code))] pub(super) fn build_unchecked($self: $self_type) -> Offer { $self.build_without_checks() } } } impl<'a, M: MetadataStrategy, T: secp256k1::Signing> OfferBuilder<'a, M, T> { - offer_builder_methods!(self, Self, Self, self); + offer_builder_methods!(self, Self, Self, self, mut); #[cfg(test)] - offer_builder_test_methods!(self, Self, Self, self); + offer_builder_test_methods!(self, Self, Self, self, mut); } impl<'a> OfferBuilder<'a, ExplicitMetadata, secp256k1::SignOnly> { @@ -368,7 +405,43 @@ impl<'a> OfferBuilder<'a, ExplicitMetadata, secp256k1::SignOnly> { } impl<'a, T: secp256k1::Signing> OfferBuilder<'a, DerivedMetadata, T> { - offer_derived_metadata_builder_methods!(); + offer_derived_metadata_builder_methods!(T); +} + +#[cfg(all(c_bindings, not(test)))] +impl<'a> OfferWithExplicitMetadataBuilder<'a> { + offer_explicit_metadata_builder_methods!(self, &mut Self, (), ()); + offer_builder_methods!(self, &mut Self, (), ()); +} + +#[cfg(all(c_bindings, test))] +impl<'a> OfferWithExplicitMetadataBuilder<'a> { + offer_explicit_metadata_builder_methods!(self, &mut Self, &mut Self, self); + offer_builder_methods!(self, &mut Self, &mut Self, self); + offer_builder_test_methods!(self, &mut Self, &mut Self, self); +} + +#[cfg(all(c_bindings, not(test)))] +impl<'a> OfferWithDerivedMetadataBuilder<'a> { + offer_derived_metadata_builder_methods!(secp256k1::All); + offer_builder_methods!(self, &mut Self, (), ()); +} + +#[cfg(all(c_bindings, test))] +impl<'a> OfferWithDerivedMetadataBuilder<'a> { + offer_derived_metadata_builder_methods!(secp256k1::All); + offer_builder_methods!(self, &mut Self, &mut Self, self); + offer_builder_test_methods!(self, &mut Self, &mut Self, self); +} + +#[cfg(c_bindings)] +impl<'a> From> +for OfferWithDerivedMetadataBuilder<'a> { + fn from(builder: OfferBuilder<'a, DerivedMetadata, secp256k1::All>) -> Self { + let OfferBuilder { 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. @@ -936,7 +1009,15 @@ impl core::fmt::Display for Offer { #[cfg(test)] mod tests { - use super::{Amount, Offer, OfferBuilder, OfferTlvStreamRef, Quantity}; + use super::{Amount, Offer, OfferTlvStreamRef, Quantity}; + #[cfg(not(c_bindings))] + use { + super::OfferBuilder, + }; + #[cfg(c_bindings)] + use { + super::OfferWithExplicitMetadataBuilder as OfferBuilder, + }; use bitcoin::blockdata::constants::ChainHash; use bitcoin::network::constants::Network; @@ -1065,6 +1146,8 @@ mod tests { let entropy = FixedEntropy {}; let secp_ctx = Secp256k1::new(); + #[cfg(c_bindings)] + use super::OfferWithDerivedMetadataBuilder as OfferBuilder; let offer = OfferBuilder ::deriving_signing_pubkey(desc, node_id, &expanded_key, &entropy, &secp_ctx) .amount_msats(1000) @@ -1121,6 +1204,8 @@ mod tests { ], }; + #[cfg(c_bindings)] + use super::OfferWithDerivedMetadataBuilder as OfferBuilder; let offer = OfferBuilder ::deriving_signing_pubkey(desc, node_id, &expanded_key, &entropy, &secp_ctx) .amount_msats(1000) @@ -1175,8 +1260,13 @@ mod tests { assert_eq!(tlv_stream.amount, Some(1000)); assert_eq!(tlv_stream.currency, None); + #[cfg(not(c_bindings))] let builder = OfferBuilder::new("foo".into(), pubkey(42)) .amount(currency_amount.clone()); + #[cfg(c_bindings)] + let mut builder = OfferBuilder::new("foo".into(), pubkey(42)); + #[cfg(c_bindings)] + builder.amount(currency_amount.clone()); let tlv_stream = builder.offer.as_tlv_stream(); assert_eq!(builder.offer.amount, Some(currency_amount.clone())); assert_eq!(tlv_stream.amount, Some(10)); From 1af5c409fe860a8ddabaf3475a29bc83fb068b8a Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Fri, 23 Feb 2024 16:52:49 -0600 Subject: [PATCH 03/13] Macro-ize RefundBuilder RefundBuilder is not exported to bindings because it has methods that take `self` by value. Define these methods using macros such that different builders and related methods can be defined for c_bindings. --- lightning/src/offers/refund.rs | 101 +++++++++++++++++++-------------- 1 file changed, 58 insertions(+), 43 deletions(-) diff --git a/lightning/src/offers/refund.rs b/lightning/src/offers/refund.rs index ba3ab1d1ef3..6d3a34f6122 100644 --- a/lightning/src/offers/refund.rs +++ b/lightning/src/offers/refund.rs @@ -124,7 +124,7 @@ pub struct RefundBuilder<'a, T: secp256k1::Signing> { secp_ctx: Option<&'a Secp256k1>, } -impl<'a> RefundBuilder<'a, secp256k1::SignOnly> { +macro_rules! refund_without_secp256k1_builder_methods { () => { /// Creates a new builder for a refund using the [`Refund::payer_id`] for the public node id to /// send to if no [`Refund::paths`] are set. Otherwise, it may be a transient pubkey. /// @@ -155,9 +155,11 @@ impl<'a> RefundBuilder<'a, secp256k1::SignOnly> { secp_ctx: None, }) } -} +} } -impl<'a, T: secp256k1::Signing> RefundBuilder<'a, T> { +macro_rules! refund_builder_methods { ( + $self: ident, $self_type: ty, $return_type: ty, $return_value: expr +) => { /// Similar to [`RefundBuilder::new`] except, if [`RefundBuilder::path`] is called, the payer id /// is derived from the given [`ExpandedKey`] and nonce. This provides sender privacy by using a /// different payer id for each refund, assuming a different nonce is used. Otherwise, the @@ -197,17 +199,17 @@ impl<'a, T: secp256k1::Signing> RefundBuilder<'a, T> { /// already passed is valid and can be checked for using [`Refund::is_expired`]. /// /// Successive calls to this method will override the previous setting. - pub fn absolute_expiry(mut self, absolute_expiry: Duration) -> Self { - self.refund.absolute_expiry = Some(absolute_expiry); - self + pub fn absolute_expiry(mut $self: $self_type, absolute_expiry: Duration) -> $return_type { + $self.refund.absolute_expiry = Some(absolute_expiry); + $return_value } /// Sets the [`Refund::issuer`]. /// /// Successive calls to this method will override the previous setting. - pub fn issuer(mut self, issuer: String) -> Self { - self.refund.issuer = Some(issuer); - self + pub fn issuer(mut $self: $self_type, issuer: String) -> $return_type { + $self.refund.issuer = Some(issuer); + $return_value } /// Adds a blinded path to [`Refund::paths`]. Must include at least one path if only connected @@ -215,26 +217,26 @@ impl<'a, T: secp256k1::Signing> RefundBuilder<'a, T> { /// /// Successive calls to this method will add another blinded path. Caller is responsible for not /// adding duplicate paths. - pub fn path(mut self, path: BlindedPath) -> Self { - self.refund.paths.get_or_insert_with(Vec::new).push(path); - self + pub fn path(mut $self: $self_type, path: BlindedPath) -> $return_type { + $self.refund.paths.get_or_insert_with(Vec::new).push(path); + $return_value } /// Sets the [`Refund::chain`] of the given [`Network`] for paying an invoice. If not /// called, [`Network::Bitcoin`] is assumed. /// /// Successive calls to this method will override the previous setting. - pub fn chain(self, network: Network) -> Self { - self.chain_hash(ChainHash::using_genesis_block(network)) + pub fn chain($self: $self_type, network: Network) -> $return_type { + $self.chain_hash(ChainHash::using_genesis_block(network)) } /// Sets the [`Refund::chain`] of the given [`ChainHash`] for paying an invoice. If not called, /// [`Network::Bitcoin`] is assumed. /// /// Successive calls to this method will override the previous setting. - pub(crate) fn chain_hash(mut self, chain: ChainHash) -> Self { - self.refund.chain = Some(chain); - self + pub(crate) fn chain_hash(mut $self: $self_type, chain: ChainHash) -> $return_type { + $self.refund.chain = Some(chain); + $return_value } /// Sets [`Refund::quantity`] of items. This is purely for informational purposes. It is useful @@ -246,66 +248,79 @@ impl<'a, T: secp256k1::Signing> RefundBuilder<'a, T> { /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice /// [`InvoiceRequest::quantity`]: crate::offers::invoice_request::InvoiceRequest::quantity /// [`Offer`]: crate::offers::offer::Offer - pub fn quantity(mut self, quantity: u64) -> Self { - self.refund.quantity = Some(quantity); - self + pub fn quantity(mut $self: $self_type, quantity: u64) -> $return_type { + $self.refund.quantity = Some(quantity); + $return_value } /// Sets the [`Refund::payer_note`]. /// /// Successive calls to this method will override the previous setting. - pub fn payer_note(mut self, payer_note: String) -> Self { - self.refund.payer_note = Some(payer_note); - self + pub fn payer_note(mut $self: $self_type, payer_note: String) -> $return_type { + $self.refund.payer_note = Some(payer_note); + $return_value } /// Builds a [`Refund`] after checking for valid semantics. - pub fn build(mut self) -> Result { - if self.refund.chain() == self.refund.implied_chain() { - self.refund.chain = None; + pub fn build(mut $self: $self_type) -> Result { + if $self.refund.chain() == $self.refund.implied_chain() { + $self.refund.chain = None; } // Create the metadata for stateless verification of a Bolt12Invoice. - if self.refund.payer.0.has_derivation_material() { - let mut metadata = core::mem::take(&mut self.refund.payer.0); + if $self.refund.payer.0.has_derivation_material() { + let mut metadata = core::mem::take(&mut $self.refund.payer.0); - if self.refund.paths.is_none() { + if $self.refund.paths.is_none() { metadata = metadata.without_keys(); } - let mut tlv_stream = self.refund.as_tlv_stream(); + let mut tlv_stream = $self.refund.as_tlv_stream(); tlv_stream.0.metadata = None; if metadata.derives_payer_keys() { tlv_stream.2.payer_id = None; } - let (derived_metadata, keys) = metadata.derive_from(tlv_stream, self.secp_ctx); + let (derived_metadata, keys) = metadata.derive_from(tlv_stream, $self.secp_ctx); metadata = derived_metadata; if let Some(keys) = keys { - self.refund.payer_id = keys.public_key(); + $self.refund.payer_id = keys.public_key(); } - self.refund.payer.0 = metadata; + $self.refund.payer.0 = metadata; } let mut bytes = Vec::new(); - self.refund.write(&mut bytes).unwrap(); + $self.refund.write(&mut bytes).unwrap(); - Ok(Refund { bytes, contents: self.refund }) + Ok(Refund { bytes, contents: $self.refund }) } -} +} } #[cfg(test)] -impl<'a, T: secp256k1::Signing> RefundBuilder<'a, T> { - pub(crate) fn clear_paths(mut self) -> Self { - self.refund.paths = None; - self +macro_rules! refund_builder_test_methods { ( + $self: ident, $self_type: ty, $return_type: ty, $return_value: expr +) => { + pub(crate) fn clear_paths(mut $self: $self_type) -> $return_type { + $self.refund.paths = None; + $return_value } - fn features_unchecked(mut self, features: InvoiceRequestFeatures) -> Self { - self.refund.features = features; - self + fn features_unchecked(mut $self: $self_type, features: InvoiceRequestFeatures) -> $return_type { + $self.refund.features = features; + $return_value } +} } + +impl<'a> RefundBuilder<'a, secp256k1::SignOnly> { + refund_without_secp256k1_builder_methods!(); +} + +impl<'a, T: secp256k1::Signing> RefundBuilder<'a, T> { + refund_builder_methods!(self, Self, Self, self); + + #[cfg(test)] + refund_builder_test_methods!(self, Self, Self, self); } /// A `Refund` is a request to send an [`Bolt12Invoice`] without a preceding [`Offer`]. From b1ad95158e8cea0119df31fb5318a31fcc70e927 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Fri, 23 Feb 2024 20:30:50 -0600 Subject: [PATCH 04/13] Add c_bindings version of RefundBuilder Use the macros introduced in the previous commit to define a builder called RefundMaybeWithDerivedMetadataBuilder. The difference between this and RefundBuilder is that this has methods that take `self` by mutable reference instead of by value and don't return anything instead returning the modified builder. This is required because bindings don't support move semantics. Because of this, the builder's contents must be cloned when building a Refund. Keeps RefundBuilder defined so that it can be used internally in ChannelManager::create_refund_builder even when compiled for c_bindings. --- lightning/src/ln/channelmanager.rs | 61 +++++++++++---------- lightning/src/offers/invoice.rs | 3 +- lightning/src/offers/refund.rs | 86 +++++++++++++++++++++++------- 3 files changed, 104 insertions(+), 46 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index d41909beb3a..c9fc44994f2 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -85,6 +85,7 @@ use { #[cfg(c_bindings)] use { crate::offers::offer::OfferWithDerivedMetadataBuilder, + crate::offers::refund::RefundMaybeWithDerivedMetadataBuilder, }; use alloc::collections::{btree_map, BTreeMap}; @@ -7574,23 +7575,7 @@ macro_rules! create_offer_builder { ($self: ident, $builder: ty) => { } } } -impl ChannelManager -where - M::Target: chain::Watch<::EcdsaSigner>, - T::Target: BroadcasterInterface, - ES::Target: EntropySource, - NS::Target: NodeSigner, - SP::Target: SignerProvider, - F::Target: FeeEstimator, - R::Target: Router, - L::Target: Logger, -{ - #[cfg(not(c_bindings))] - create_offer_builder!(self, OfferBuilder); - - #[cfg(c_bindings)] - create_offer_builder!(self, OfferWithDerivedMetadataBuilder); - +macro_rules! create_refund_builder { ($self: ident, $builder: ty) => { /// Creates a [`RefundBuilder`] such that the [`Refund`] it builds is recognized by the /// [`ChannelManager`] when handling [`Bolt12Invoice`] messages for the refund. /// @@ -7640,31 +7625,53 @@ where /// [`Bolt12Invoice::payment_paths`]: crate::offers::invoice::Bolt12Invoice::payment_paths /// [Avoiding Duplicate Payments]: #avoiding-duplicate-payments pub fn create_refund_builder( - &self, description: String, amount_msats: u64, absolute_expiry: Duration, + &$self, description: String, amount_msats: u64, absolute_expiry: Duration, payment_id: PaymentId, retry_strategy: Retry, max_total_routing_fee_msat: Option - ) -> Result, Bolt12SemanticError> { - let node_id = self.get_our_node_id(); - let expanded_key = &self.inbound_payment_key; - let entropy = &*self.entropy_source; - let secp_ctx = &self.secp_ctx; + ) -> Result<$builder, Bolt12SemanticError> { + let node_id = $self.get_our_node_id(); + let expanded_key = &$self.inbound_payment_key; + let entropy = &*$self.entropy_source; + let secp_ctx = &$self.secp_ctx; - let path = self.create_blinded_path().map_err(|_| Bolt12SemanticError::MissingPaths)?; + let path = $self.create_blinded_path().map_err(|_| Bolt12SemanticError::MissingPaths)?; let builder = RefundBuilder::deriving_payer_id( description, node_id, expanded_key, entropy, secp_ctx, amount_msats, payment_id )? - .chain_hash(self.chain_hash) + .chain_hash($self.chain_hash) .absolute_expiry(absolute_expiry) .path(path); let expiration = StaleExpiration::AbsoluteTimeout(absolute_expiry); - self.pending_outbound_payments + $self.pending_outbound_payments .add_new_awaiting_invoice( payment_id, expiration, retry_strategy, max_total_routing_fee_msat, ) .map_err(|_| Bolt12SemanticError::DuplicatePaymentId)?; - Ok(builder) + Ok(builder.into()) } +} } + +impl ChannelManager +where + M::Target: chain::Watch<::EcdsaSigner>, + T::Target: BroadcasterInterface, + ES::Target: EntropySource, + NS::Target: NodeSigner, + SP::Target: SignerProvider, + F::Target: FeeEstimator, + R::Target: Router, + L::Target: Logger, +{ + #[cfg(not(c_bindings))] + create_offer_builder!(self, OfferBuilder); + #[cfg(not(c_bindings))] + create_refund_builder!(self, RefundBuilder); + + #[cfg(c_bindings)] + create_offer_builder!(self, OfferWithDerivedMetadataBuilder); + #[cfg(c_bindings)] + create_refund_builder!(self, RefundMaybeWithDerivedMetadataBuilder); /// Pays for an [`Offer`] using the given parameters by creating an [`InvoiceRequest`] and /// enqueuing it to be sent via an onion message. [`ChannelManager`] will pay the actual diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index ebb23fb2ee0..da88be15458 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -1310,14 +1310,15 @@ mod tests { #[cfg(not(c_bindings))] use { crate::offers::offer::OfferBuilder, + crate::offers::refund::RefundBuilder, }; #[cfg(c_bindings)] use { crate::offers::offer::OfferWithExplicitMetadataBuilder as OfferBuilder, + crate::offers::refund::RefundMaybeWithDerivedMetadataBuilder as RefundBuilder, }; use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError}; use crate::offers::payer::PayerTlvStreamRef; - use crate::offers::refund::RefundBuilder; use crate::offers::test_utils::*; use crate::util::ser::{BigSize, Iterable, Writeable}; use crate::util::string::PrintableString; diff --git a/lightning/src/offers/refund.rs b/lightning/src/offers/refund.rs index 6d3a34f6122..f4e9c4a8cc7 100644 --- a/lightning/src/offers/refund.rs +++ b/lightning/src/offers/refund.rs @@ -124,7 +124,18 @@ pub struct RefundBuilder<'a, T: secp256k1::Signing> { secp_ctx: Option<&'a Secp256k1>, } -macro_rules! refund_without_secp256k1_builder_methods { () => { +/// Builds a [`Refund`] for the "offer for money" flow. +/// +/// See [module-level documentation] for usage. +/// +/// [module-level documentation]: self +#[cfg(c_bindings)] +pub struct RefundMaybeWithDerivedMetadataBuilder<'a> { + refund: RefundContents, + secp_ctx: Option<&'a Secp256k1>, +} + +macro_rules! refund_explicit_metadata_builder_methods { () => { /// Creates a new builder for a refund using the [`Refund::payer_id`] for the public node id to /// send to if no [`Refund::paths`] are set. Otherwise, it may be a transient pubkey. /// @@ -158,7 +169,7 @@ macro_rules! refund_without_secp256k1_builder_methods { () => { } } macro_rules! refund_builder_methods { ( - $self: ident, $self_type: ty, $return_type: ty, $return_value: expr + $self: ident, $self_type: ty, $return_type: ty, $return_value: expr, $secp_context: ty $(, $self_mut: tt)? ) => { /// Similar to [`RefundBuilder::new`] except, if [`RefundBuilder::path`] is called, the payer id /// is derived from the given [`ExpandedKey`] and nonce. This provides sender privacy by using a @@ -175,7 +186,7 @@ macro_rules! refund_builder_methods { ( /// [`ExpandedKey`]: crate::ln::inbound_payment::ExpandedKey pub fn deriving_payer_id( description: String, node_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES, - secp_ctx: &'a Secp256k1, amount_msats: u64, payment_id: PaymentId + secp_ctx: &'a Secp256k1<$secp_context>, amount_msats: u64, payment_id: PaymentId ) -> Result where ES::Target: EntropySource { if amount_msats > MAX_VALUE_MSAT { return Err(Bolt12SemanticError::InvalidAmount); @@ -199,7 +210,7 @@ macro_rules! refund_builder_methods { ( /// already passed is valid and can be checked for using [`Refund::is_expired`]. /// /// Successive calls to this method will override the previous setting. - pub fn absolute_expiry(mut $self: $self_type, absolute_expiry: Duration) -> $return_type { + pub fn absolute_expiry($($self_mut)* $self: $self_type, absolute_expiry: Duration) -> $return_type { $self.refund.absolute_expiry = Some(absolute_expiry); $return_value } @@ -207,7 +218,7 @@ macro_rules! refund_builder_methods { ( /// Sets the [`Refund::issuer`]. /// /// Successive calls to this method will override the previous setting. - pub fn issuer(mut $self: $self_type, issuer: String) -> $return_type { + pub fn issuer($($self_mut)* $self: $self_type, issuer: String) -> $return_type { $self.refund.issuer = Some(issuer); $return_value } @@ -217,7 +228,7 @@ macro_rules! refund_builder_methods { ( /// /// Successive calls to this method will add another blinded path. Caller is responsible for not /// adding duplicate paths. - pub fn path(mut $self: $self_type, path: BlindedPath) -> $return_type { + pub fn path($($self_mut)* $self: $self_type, path: BlindedPath) -> $return_type { $self.refund.paths.get_or_insert_with(Vec::new).push(path); $return_value } @@ -234,7 +245,7 @@ macro_rules! refund_builder_methods { ( /// [`Network::Bitcoin`] is assumed. /// /// Successive calls to this method will override the previous setting. - pub(crate) fn chain_hash(mut $self: $self_type, chain: ChainHash) -> $return_type { + pub(crate) fn chain_hash($($self_mut)* $self: $self_type, chain: ChainHash) -> $return_type { $self.refund.chain = Some(chain); $return_value } @@ -248,7 +259,7 @@ macro_rules! refund_builder_methods { ( /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice /// [`InvoiceRequest::quantity`]: crate::offers::invoice_request::InvoiceRequest::quantity /// [`Offer`]: crate::offers::offer::Offer - pub fn quantity(mut $self: $self_type, quantity: u64) -> $return_type { + pub fn quantity($($self_mut)* $self: $self_type, quantity: u64) -> $return_type { $self.refund.quantity = Some(quantity); $return_value } @@ -256,13 +267,13 @@ macro_rules! refund_builder_methods { ( /// Sets the [`Refund::payer_note`]. /// /// Successive calls to this method will override the previous setting. - pub fn payer_note(mut $self: $self_type, payer_note: String) -> $return_type { + pub fn payer_note($($self_mut)* $self: $self_type, payer_note: String) -> $return_type { $self.refund.payer_note = Some(payer_note); $return_value } /// Builds a [`Refund`] after checking for valid semantics. - pub fn build(mut $self: $self_type) -> Result { + pub fn build($($self_mut)* $self: $self_type) -> Result { if $self.refund.chain() == $self.refund.implied_chain() { $self.refund.chain = None; } @@ -293,34 +304,65 @@ macro_rules! refund_builder_methods { ( let mut bytes = Vec::new(); $self.refund.write(&mut bytes).unwrap(); - Ok(Refund { bytes, contents: $self.refund }) + Ok(Refund { + bytes, + #[cfg(not(c_bindings))] + contents: $self.refund, + #[cfg(c_bindings)] + contents: $self.refund.clone(), + }) } } } #[cfg(test)] macro_rules! refund_builder_test_methods { ( - $self: ident, $self_type: ty, $return_type: ty, $return_value: expr + $self: ident, $self_type: ty, $return_type: ty, $return_value: expr $(, $self_mut: tt)? ) => { - pub(crate) fn clear_paths(mut $self: $self_type) -> $return_type { + #[cfg_attr(c_bindings, allow(dead_code))] + pub(crate) fn clear_paths($($self_mut)* $self: $self_type) -> $return_type { $self.refund.paths = None; $return_value } - fn features_unchecked(mut $self: $self_type, features: InvoiceRequestFeatures) -> $return_type { + #[cfg_attr(c_bindings, allow(dead_code))] + fn features_unchecked($($self_mut)* $self: $self_type, features: InvoiceRequestFeatures) -> $return_type { $self.refund.features = features; $return_value } } } impl<'a> RefundBuilder<'a, secp256k1::SignOnly> { - refund_without_secp256k1_builder_methods!(); + refund_explicit_metadata_builder_methods!(); } impl<'a, T: secp256k1::Signing> RefundBuilder<'a, T> { - refund_builder_methods!(self, Self, Self, self); + refund_builder_methods!(self, Self, Self, self, T, mut); #[cfg(test)] - refund_builder_test_methods!(self, Self, Self, self); + refund_builder_test_methods!(self, Self, Self, self, mut); +} + +#[cfg(all(c_bindings, not(test)))] +impl<'a> RefundMaybeWithDerivedMetadataBuilder<'a> { + refund_explicit_metadata_builder_methods!(); + refund_builder_methods!(self, &mut Self, (), (), secp256k1::All); +} + +#[cfg(all(c_bindings, test))] +impl<'a> RefundMaybeWithDerivedMetadataBuilder<'a> { + refund_explicit_metadata_builder_methods!(); + refund_builder_methods!(self, &mut Self, &mut Self, self, secp256k1::All); + refund_builder_test_methods!(self, &mut Self, &mut Self, self); +} + +#[cfg(c_bindings)] +impl<'a> From> +for RefundMaybeWithDerivedMetadataBuilder<'a> { + fn from(builder: RefundBuilder<'a, secp256k1::All>) -> Self { + let RefundBuilder { refund, secp_ctx } = builder; + + Self { refund, secp_ctx } + } } /// A `Refund` is a request to send an [`Bolt12Invoice`] without a preceding [`Offer`]. @@ -798,7 +840,15 @@ impl core::fmt::Display for Refund { #[cfg(test)] mod tests { - use super::{Refund, RefundBuilder, RefundTlvStreamRef}; + use super::{Refund, RefundTlvStreamRef}; + #[cfg(not(c_bindings))] + use { + super::RefundBuilder, + }; + #[cfg(c_bindings)] + use { + super::RefundMaybeWithDerivedMetadataBuilder as RefundBuilder, + }; use bitcoin::blockdata::constants::ChainHash; use bitcoin::network::constants::Network; From 702a003270a452ee65daf28869b77f1f63c24caa Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Thu, 22 Feb 2024 15:13:32 -0600 Subject: [PATCH 05/13] Merge similar InvoiceRequestBuilder impl blocks This avoids needing to create additional macros when adding c_bindings support. --- lightning/src/offers/invoice_request.rs | 50 ++++++++++++------------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index ecdde2b6657..bbacd99d8f8 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -144,6 +144,14 @@ impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, ExplicitPayerI secp_ctx: None, } } + + /// Builds an unsigned [`InvoiceRequest`] after checking for valid semantics. It can be signed + /// by [`UnsignedInvoiceRequest::sign`]. + pub fn build(self) -> Result { + let (unsigned_invoice_request, keys, _) = self.build_with_checks()?; + debug_assert!(keys.is_none()); + Ok(unsigned_invoice_request) + } } impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, DerivedPayerId, T> { @@ -163,6 +171,21 @@ impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, DerivedPayerId secp_ctx: Some(secp_ctx), } } + + /// Builds a signed [`InvoiceRequest`] after checking for valid semantics. + pub fn build_and_sign(self) -> Result { + let (unsigned_invoice_request, keys, secp_ctx) = self.build_with_checks()?; + debug_assert!(keys.is_some()); + + let secp_ctx = secp_ctx.unwrap(); + let keys = keys.unwrap(); + let invoice_request = unsigned_invoice_request + .sign::<_, Infallible>( + |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) + ) + .unwrap(); + Ok(invoice_request) + } } impl<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, P, T> { @@ -301,33 +324,6 @@ impl<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signing> InvoiceRequestBuilder<'a } } -impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, ExplicitPayerId, T> { - /// Builds an unsigned [`InvoiceRequest`] after checking for valid semantics. It can be signed - /// by [`UnsignedInvoiceRequest::sign`]. - pub fn build(self) -> Result { - let (unsigned_invoice_request, keys, _) = self.build_with_checks()?; - debug_assert!(keys.is_none()); - Ok(unsigned_invoice_request) - } -} - -impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, DerivedPayerId, T> { - /// Builds a signed [`InvoiceRequest`] after checking for valid semantics. - pub fn build_and_sign(self) -> Result { - let (unsigned_invoice_request, keys, secp_ctx) = self.build_with_checks()?; - debug_assert!(keys.is_some()); - - let secp_ctx = secp_ctx.unwrap(); - let keys = keys.unwrap(); - let invoice_request = unsigned_invoice_request - .sign::<_, Infallible>( - |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) - ) - .unwrap(); - Ok(invoice_request) - } -} - #[cfg(test)] impl<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, P, T> { fn chain_unchecked(mut self, network: Network) -> Self { From c7b877efdde29ddfce072dc5ba5eef33f344935c Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Thu, 22 Feb 2024 16:47:38 -0600 Subject: [PATCH 06/13] Macro-ize InvoiceRequestBuilder InvoiceRequestBuilder is not exported to bindings because it has methods that take `self` by value and are only implemented for certain type parameterizations. Define these methods using macros such that different builders and related methods can be defined for c_bindings. --- lightning/src/offers/invoice_request.rs | 175 ++++++++++++++---------- lightning/src/offers/offer.rs | 41 +++--- 2 files changed, 124 insertions(+), 92 deletions(-) diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index bbacd99d8f8..02fcd578f94 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -117,7 +117,7 @@ pub struct DerivedPayerId {} impl PayerIdStrategy for ExplicitPayerId {} impl PayerIdStrategy for DerivedPayerId {} -impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, ExplicitPayerId, T> { +macro_rules! invoice_request_explicit_payer_id_builder_methods { ($self: ident, $self_type: ty) => { pub(super) fn new(offer: &'a Offer, metadata: Vec, payer_id: PublicKey) -> Self { Self { offer, @@ -147,14 +147,14 @@ impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, ExplicitPayerI /// Builds an unsigned [`InvoiceRequest`] after checking for valid semantics. It can be signed /// by [`UnsignedInvoiceRequest::sign`]. - pub fn build(self) -> Result { - let (unsigned_invoice_request, keys, _) = self.build_with_checks()?; + pub fn build($self: $self_type) -> Result { + let (unsigned_invoice_request, keys, _) = $self.build_with_checks()?; debug_assert!(keys.is_none()); Ok(unsigned_invoice_request) } -} +} } -impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, DerivedPayerId, T> { +macro_rules! invoice_request_derived_payer_id_builder_methods { ($self: ident, $self_type: ty) => { pub(super) fn deriving_payer_id( offer: &'a Offer, expanded_key: &ExpandedKey, entropy_source: ES, secp_ctx: &'b Secp256k1, payment_id: PaymentId @@ -173,8 +173,8 @@ impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, DerivedPayerId } /// Builds a signed [`InvoiceRequest`] after checking for valid semantics. - pub fn build_and_sign(self) -> Result { - let (unsigned_invoice_request, keys, secp_ctx) = self.build_with_checks()?; + pub fn build_and_sign($self: $self_type) -> Result { + let (unsigned_invoice_request, keys, secp_ctx) = $self.build_with_checks()?; debug_assert!(keys.is_some()); let secp_ctx = secp_ctx.unwrap(); @@ -186,9 +186,11 @@ impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, DerivedPayerId .unwrap(); Ok(invoice_request) } -} +} } -impl<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, P, T> { +macro_rules! invoice_request_builder_methods { ( + $self: ident, $self_type: ty, $return_type: ty, $return_value: expr +) => { fn create_contents(offer: &Offer, metadata: Metadata) -> InvoiceRequestContentsWithoutPayerId { let offer = offer.contents.clone(); InvoiceRequestContentsWithoutPayerId { @@ -202,8 +204,8 @@ impl<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signing> InvoiceRequestBuilder<'a /// by the offer. /// /// Successive calls to this method will override the previous setting. - pub fn chain(self, network: Network) -> Result { - self.chain_hash(ChainHash::using_genesis_block(network)) + pub fn chain($self: $self_type, network: Network) -> Result<$return_type, Bolt12SemanticError> { + $self.chain_hash(ChainHash::using_genesis_block(network)) } /// Sets the [`InvoiceRequest::chain`] for paying an invoice. If not called, the chain hash of @@ -211,13 +213,13 @@ impl<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signing> InvoiceRequestBuilder<'a /// offer. /// /// Successive calls to this method will override the previous setting. - pub(crate) fn chain_hash(mut self, chain: ChainHash) -> Result { - if !self.offer.supports_chain(chain) { + pub(crate) fn chain_hash(mut $self: $self_type, chain: ChainHash) -> Result<$return_type, Bolt12SemanticError> { + if !$self.offer.supports_chain(chain) { return Err(Bolt12SemanticError::UnsupportedChain); } - self.invoice_request.chain = Some(chain); - Ok(self) + $self.invoice_request.chain = Some(chain); + Ok($return_value) } /// Sets the [`InvoiceRequest::amount_msats`] for paying an invoice. Errors if `amount_msats` is @@ -226,130 +228,147 @@ impl<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signing> InvoiceRequestBuilder<'a /// Successive calls to this method will override the previous setting. /// /// [`quantity`]: Self::quantity - pub fn amount_msats(mut self, amount_msats: u64) -> Result { - self.invoice_request.offer.check_amount_msats_for_quantity( - Some(amount_msats), self.invoice_request.quantity + pub fn amount_msats(mut $self: $self_type, amount_msats: u64) -> Result<$return_type, Bolt12SemanticError> { + $self.invoice_request.offer.check_amount_msats_for_quantity( + Some(amount_msats), $self.invoice_request.quantity )?; - self.invoice_request.amount_msats = Some(amount_msats); - Ok(self) + $self.invoice_request.amount_msats = Some(amount_msats); + Ok($return_value) } /// Sets [`InvoiceRequest::quantity`] of items. If not set, `1` is assumed. Errors if `quantity` /// does not conform to [`Offer::is_valid_quantity`]. /// /// Successive calls to this method will override the previous setting. - pub fn quantity(mut self, quantity: u64) -> Result { - self.invoice_request.offer.check_quantity(Some(quantity))?; - self.invoice_request.quantity = Some(quantity); - Ok(self) + pub fn quantity(mut $self: $self_type, quantity: u64) -> Result<$return_type, Bolt12SemanticError> { + $self.invoice_request.offer.check_quantity(Some(quantity))?; + $self.invoice_request.quantity = Some(quantity); + Ok($return_value) } /// Sets the [`InvoiceRequest::payer_note`]. /// /// Successive calls to this method will override the previous setting. - pub fn payer_note(mut self, payer_note: String) -> Self { - self.invoice_request.payer_note = Some(payer_note); - self + pub fn payer_note(mut $self: $self_type, payer_note: String) -> $return_type { + $self.invoice_request.payer_note = Some(payer_note); + $return_value } - fn build_with_checks(mut self) -> Result< + fn build_with_checks(mut $self: $self_type) -> Result< (UnsignedInvoiceRequest, Option, Option<&'b Secp256k1>), Bolt12SemanticError > { #[cfg(feature = "std")] { - if self.offer.is_expired() { + if $self.offer.is_expired() { return Err(Bolt12SemanticError::AlreadyExpired); } } - let chain = self.invoice_request.chain(); - if !self.offer.supports_chain(chain) { + let chain = $self.invoice_request.chain(); + if !$self.offer.supports_chain(chain) { return Err(Bolt12SemanticError::UnsupportedChain); } - if chain == self.offer.implied_chain() { - self.invoice_request.chain = None; + if chain == $self.offer.implied_chain() { + $self.invoice_request.chain = None; } - if self.offer.amount().is_none() && self.invoice_request.amount_msats.is_none() { + if $self.offer.amount().is_none() && $self.invoice_request.amount_msats.is_none() { return Err(Bolt12SemanticError::MissingAmount); } - self.invoice_request.offer.check_quantity(self.invoice_request.quantity)?; - self.invoice_request.offer.check_amount_msats_for_quantity( - self.invoice_request.amount_msats, self.invoice_request.quantity + $self.invoice_request.offer.check_quantity($self.invoice_request.quantity)?; + $self.invoice_request.offer.check_amount_msats_for_quantity( + $self.invoice_request.amount_msats, $self.invoice_request.quantity )?; - Ok(self.build_without_checks()) + Ok($self.build_without_checks()) } - fn build_without_checks(mut self) -> + fn build_without_checks(mut $self: $self_type) -> (UnsignedInvoiceRequest, Option, Option<&'b Secp256k1>) { // Create the metadata for stateless verification of a Bolt12Invoice. let mut keys = None; - let secp_ctx = self.secp_ctx.clone(); - if self.invoice_request.payer.0.has_derivation_material() { - let mut metadata = core::mem::take(&mut self.invoice_request.payer.0); + let secp_ctx = $self.secp_ctx.clone(); + if $self.invoice_request.payer.0.has_derivation_material() { + let mut metadata = core::mem::take(&mut $self.invoice_request.payer.0); - let mut tlv_stream = self.invoice_request.as_tlv_stream(); + let mut tlv_stream = $self.invoice_request.as_tlv_stream(); debug_assert!(tlv_stream.2.payer_id.is_none()); tlv_stream.0.metadata = None; if !metadata.derives_payer_keys() { - tlv_stream.2.payer_id = self.payer_id.as_ref(); + tlv_stream.2.payer_id = $self.payer_id.as_ref(); } - let (derived_metadata, derived_keys) = metadata.derive_from(tlv_stream, self.secp_ctx); + let (derived_metadata, derived_keys) = metadata.derive_from(tlv_stream, $self.secp_ctx); metadata = derived_metadata; keys = derived_keys; if let Some(keys) = keys { - debug_assert!(self.payer_id.is_none()); - self.payer_id = Some(keys.public_key()); + debug_assert!($self.payer_id.is_none()); + $self.payer_id = Some(keys.public_key()); } - self.invoice_request.payer.0 = metadata; + $self.invoice_request.payer.0 = metadata; } - debug_assert!(self.invoice_request.payer.0.as_bytes().is_some()); - debug_assert!(self.payer_id.is_some()); - let payer_id = self.payer_id.unwrap(); + debug_assert!($self.invoice_request.payer.0.as_bytes().is_some()); + debug_assert!($self.payer_id.is_some()); + let payer_id = $self.payer_id.unwrap(); let invoice_request = InvoiceRequestContents { - inner: self.invoice_request, + inner: $self.invoice_request, payer_id, }; - let unsigned_invoice_request = UnsignedInvoiceRequest::new(self.offer, invoice_request); + let unsigned_invoice_request = UnsignedInvoiceRequest::new($self.offer, invoice_request); (unsigned_invoice_request, keys, secp_ctx) } -} +} } #[cfg(test)] -impl<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, P, T> { - fn chain_unchecked(mut self, network: Network) -> Self { +macro_rules! invoice_request_builder_test_methods { ( + $self: ident, $self_type: ty, $return_type: ty, $return_value: expr +) => { + fn chain_unchecked(mut $self: $self_type, network: Network) -> $return_type { let chain = ChainHash::using_genesis_block(network); - self.invoice_request.chain = Some(chain); - self + $self.invoice_request.chain = Some(chain); + $return_value } - fn amount_msats_unchecked(mut self, amount_msats: u64) -> Self { - self.invoice_request.amount_msats = Some(amount_msats); - self + fn amount_msats_unchecked(mut $self: $self_type, amount_msats: u64) -> $return_type { + $self.invoice_request.amount_msats = Some(amount_msats); + $return_value } - fn features_unchecked(mut self, features: InvoiceRequestFeatures) -> Self { - self.invoice_request.features = features; - self + fn features_unchecked(mut $self: $self_type, features: InvoiceRequestFeatures) -> $return_type { + $self.invoice_request.features = features; + $return_value } - fn quantity_unchecked(mut self, quantity: u64) -> Self { - self.invoice_request.quantity = Some(quantity); - self + fn quantity_unchecked(mut $self: $self_type, quantity: u64) -> $return_type { + $self.invoice_request.quantity = Some(quantity); + $return_value } - pub(super) fn build_unchecked(self) -> UnsignedInvoiceRequest { - self.build_without_checks().0 + pub(super) fn build_unchecked($self: $self_type) -> UnsignedInvoiceRequest { + $self.build_without_checks().0 } +} } + +impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, ExplicitPayerId, T> { + invoice_request_explicit_payer_id_builder_methods!(self, Self); +} + +impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, DerivedPayerId, T> { + invoice_request_derived_payer_id_builder_methods!(self, Self); +} + +impl<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, P, T> { + invoice_request_builder_methods!(self, Self, Self, self); + + #[cfg(test)] + invoice_request_builder_test_methods!(self, Self, Self, self); } /// A semantically valid [`InvoiceRequest`] that hasn't been signed. @@ -385,31 +404,37 @@ impl UnsignedInvoiceRequest { pub fn tagged_hash(&self) -> &TaggedHash { &self.tagged_hash } +} +macro_rules! unsigned_invoice_request_sign_method { ($self: ident, $self_type: ty) => { /// Signs the [`TaggedHash`] of the invoice request using the given function. /// /// Note: The hash computation may have included unknown, odd TLV records. /// /// This is not exported to bindings users as functions are not yet mapped. - pub fn sign(mut self, sign: F) -> Result> + pub fn sign(mut $self: $self_type, sign: F) -> Result> where F: FnOnce(&Self) -> Result { - let pubkey = self.contents.payer_id; - let signature = merkle::sign_message(sign, &self, pubkey)?; + let pubkey = $self.contents.payer_id; + let signature = merkle::sign_message(sign, &$self, pubkey)?; // Append the signature TLV record to the bytes. let signature_tlv_stream = SignatureTlvStreamRef { signature: Some(&signature), }; - signature_tlv_stream.write(&mut self.bytes).unwrap(); + signature_tlv_stream.write(&mut $self.bytes).unwrap(); Ok(InvoiceRequest { - bytes: self.bytes, - contents: self.contents, + bytes: $self.bytes, + contents: $self.contents, signature, }) } +} } + +impl UnsignedInvoiceRequest { + unsigned_invoice_request_sign_method!(self, Self); } impl AsRef for UnsignedInvoiceRequest { diff --git a/lightning/src/offers/offer.rs b/lightning/src/offers/offer.rs index 5b5b66e32b6..be15272761a 100644 --- a/lightning/src/offers/offer.rs +++ b/lightning/src/offers/offer.rs @@ -584,7 +584,9 @@ impl Offer { 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, @@ -603,21 +605,21 @@ impl Offer { /// [`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, + &'a $self, expanded_key: &ExpandedKey, entropy_source: ES, secp_ctx: &'b Secp256k1, payment_id: PaymentId - ) -> Result, 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. /// @@ -627,19 +629,17 @@ impl Offer { /// /// [`InvoiceRequest::payer_id`]: crate::offers::invoice_request::InvoiceRequest::payer_id pub fn request_invoice_deriving_metadata( - &self, payer_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES, + &$self, payer_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES, payment_id: PaymentId - ) -> Result, 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`, @@ -658,16 +658,23 @@ impl Offer { /// /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest pub fn request_invoice( - &self, metadata: Vec, payer_id: PublicKey - ) -> Result, Bolt12SemanticError> { - if self.offer_features().requires_unknown_bits() { + &$self, metadata: Vec, 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)] +impl Offer { + request_invoice_derived_payer_id!(self, InvoiceRequestBuilder<'a, 'b, DerivedPayerId, T>); + request_invoice_explicit_payer_id!(self, InvoiceRequestBuilder); +} + +#[cfg(test)] +impl Offer { pub(super) fn as_tlv_stream(&self) -> OfferTlvStreamRef { self.contents.as_tlv_stream() } From af318b6efb1398099fc9c42f630b74abcc0b4fba Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Fri, 23 Feb 2024 13:34:24 -0600 Subject: [PATCH 07/13] Add c_bindings version of InvoiceRequestBuilder Use the macros introduced in the previous commit to define two builders for each type parameterization of InvoiceRequestBuilder - InvoiceRequestWithExplicitPayerIdBuilder - InvoiceRequestWithDerivedPayerIdBuilder The difference between these and InvoiceRequestBuilder is that these have methods that take `self` by mutable reference instead of by value and don't return anything instead returning the modified builder. This is required because bindings don't support move semantics nor impl blocks specific to a certain type parameterization. Because of this, the builder's contents must be cloned when building an InvoiceRequest. Keeps InvoiceRequestBuilder defined so that it can be used internally in ChannelManager::pay_for_offer even when compiled for c_bindings. --- lightning/src/ln/channelmanager.rs | 7 +- lightning/src/offers/invoice_request.rs | 161 ++++++++++++++++++++---- lightning/src/offers/offer.rs | 35 ++++-- 3 files changed, 170 insertions(+), 33 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index c9fc44994f2..87428ca36aa 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -59,6 +59,7 @@ use crate::ln::outbound_payment::{Bolt12PaymentError, OutboundPayments, PaymentA use crate::ln::wire::Encode; use crate::offers::invoice::{BlindedPayInfo, Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, DerivedSigningPubkey, InvoiceBuilder}; use crate::offers::invoice_error::InvoiceError; +use crate::offers::invoice_request::{DerivedPayerId, InvoiceRequestBuilder}; use crate::offers::merkle::SignError; use crate::offers::offer::{Offer, OfferBuilder}; use crate::offers::parse::Bolt12SemanticError; @@ -7735,9 +7736,11 @@ where let entropy = &*self.entropy_source; let secp_ctx = &self.secp_ctx; - let builder = offer + let builder: InvoiceRequestBuilder = offer .request_invoice_deriving_payer_id(expanded_key, entropy, secp_ctx, payment_id)? - .chain_hash(self.chain_hash)?; + .into(); + let builder = builder.chain_hash(self.chain_hash)?; + let builder = match quantity { None => builder, Some(quantity) => builder.quantity(quantity)?, diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index 02fcd578f94..792214d5250 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -36,9 +36,12 @@ //! let pubkey = PublicKey::from(keys); //! let mut buffer = Vec::new(); //! +//! # use lightning::offers::invoice_request::{ExplicitPayerId, InvoiceRequestBuilder}; +//! # >::from( //! "lno1qcp4256ypq" //! .parse::()? //! .request_invoice(vec![42; 64], pubkey)? +//! # ) //! .chain(Network::Testnet)? //! .amount_msats(1000)? //! .quantity(5)? @@ -99,6 +102,34 @@ pub struct InvoiceRequestBuilder<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signi secp_ctx: Option<&'b Secp256k1>, } +/// Builds an [`InvoiceRequest`] from an [`Offer`] for the "offer to be paid" flow. +/// +/// See [module-level documentation] for usage. +/// +/// [module-level documentation]: self +#[cfg(c_bindings)] +pub struct InvoiceRequestWithExplicitPayerIdBuilder<'a, 'b> { + offer: &'a Offer, + invoice_request: InvoiceRequestContentsWithoutPayerId, + payer_id: Option, + payer_id_strategy: core::marker::PhantomData, + secp_ctx: Option<&'b Secp256k1>, +} + +/// Builds an [`InvoiceRequest`] from an [`Offer`] for the "offer to be paid" flow. +/// +/// See [module-level documentation] for usage. +/// +/// [module-level documentation]: self +#[cfg(c_bindings)] +pub struct InvoiceRequestWithDerivedPayerIdBuilder<'a, 'b> { + offer: &'a Offer, + invoice_request: InvoiceRequestContentsWithoutPayerId, + payer_id: Option, + payer_id_strategy: core::marker::PhantomData, + secp_ctx: Option<&'b Secp256k1>, +} + /// Indicates how [`InvoiceRequest::payer_id`] will be set. /// /// This is not exported to bindings users as builder patterns don't map outside of move semantics. @@ -118,6 +149,7 @@ impl PayerIdStrategy for ExplicitPayerId {} impl PayerIdStrategy for DerivedPayerId {} macro_rules! invoice_request_explicit_payer_id_builder_methods { ($self: ident, $self_type: ty) => { + #[cfg_attr(c_bindings, allow(dead_code))] pub(super) fn new(offer: &'a Offer, metadata: Vec, payer_id: PublicKey) -> Self { Self { offer, @@ -128,6 +160,7 @@ macro_rules! invoice_request_explicit_payer_id_builder_methods { ($self: ident, } } + #[cfg_attr(c_bindings, allow(dead_code))] pub(super) fn deriving_metadata( offer: &'a Offer, payer_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES, payment_id: PaymentId, @@ -154,10 +187,13 @@ macro_rules! invoice_request_explicit_payer_id_builder_methods { ($self: ident, } } } -macro_rules! invoice_request_derived_payer_id_builder_methods { ($self: ident, $self_type: ty) => { +macro_rules! invoice_request_derived_payer_id_builder_methods { ( + $self: ident, $self_type: ty, $secp_context: ty +) => { + #[cfg_attr(c_bindings, allow(dead_code))] pub(super) fn deriving_payer_id( offer: &'a Offer, expanded_key: &ExpandedKey, entropy_source: ES, - secp_ctx: &'b Secp256k1, payment_id: PaymentId + secp_ctx: &'b Secp256k1<$secp_context>, payment_id: PaymentId ) -> Self where ES::Target: EntropySource { let nonce = Nonce::from_entropy_source(entropy_source); let payment_id = Some(payment_id); @@ -175,6 +211,8 @@ macro_rules! invoice_request_derived_payer_id_builder_methods { ($self: ident, $ /// Builds a signed [`InvoiceRequest`] after checking for valid semantics. pub fn build_and_sign($self: $self_type) -> Result { let (unsigned_invoice_request, keys, secp_ctx) = $self.build_with_checks()?; + #[cfg(c_bindings)] + let mut unsigned_invoice_request = unsigned_invoice_request; debug_assert!(keys.is_some()); let secp_ctx = secp_ctx.unwrap(); @@ -189,8 +227,9 @@ macro_rules! invoice_request_derived_payer_id_builder_methods { ($self: ident, $ } } macro_rules! invoice_request_builder_methods { ( - $self: ident, $self_type: ty, $return_type: ty, $return_value: expr + $self: ident, $self_type: ty, $return_type: ty, $return_value: expr, $secp_context: ty $(, $self_mut: tt)? ) => { + #[cfg_attr(c_bindings, allow(dead_code))] fn create_contents(offer: &Offer, metadata: Metadata) -> InvoiceRequestContentsWithoutPayerId { let offer = offer.contents.clone(); InvoiceRequestContentsWithoutPayerId { @@ -213,7 +252,7 @@ macro_rules! invoice_request_builder_methods { ( /// offer. /// /// Successive calls to this method will override the previous setting. - pub(crate) fn chain_hash(mut $self: $self_type, chain: ChainHash) -> Result<$return_type, Bolt12SemanticError> { + pub(crate) fn chain_hash($($self_mut)* $self: $self_type, chain: ChainHash) -> Result<$return_type, Bolt12SemanticError> { if !$self.offer.supports_chain(chain) { return Err(Bolt12SemanticError::UnsupportedChain); } @@ -228,7 +267,7 @@ macro_rules! invoice_request_builder_methods { ( /// Successive calls to this method will override the previous setting. /// /// [`quantity`]: Self::quantity - pub fn amount_msats(mut $self: $self_type, amount_msats: u64) -> Result<$return_type, Bolt12SemanticError> { + pub fn amount_msats($($self_mut)* $self: $self_type, amount_msats: u64) -> Result<$return_type, Bolt12SemanticError> { $self.invoice_request.offer.check_amount_msats_for_quantity( Some(amount_msats), $self.invoice_request.quantity )?; @@ -240,7 +279,7 @@ macro_rules! invoice_request_builder_methods { ( /// does not conform to [`Offer::is_valid_quantity`]. /// /// Successive calls to this method will override the previous setting. - pub fn quantity(mut $self: $self_type, quantity: u64) -> Result<$return_type, Bolt12SemanticError> { + pub fn quantity($($self_mut)* $self: $self_type, quantity: u64) -> Result<$return_type, Bolt12SemanticError> { $self.invoice_request.offer.check_quantity(Some(quantity))?; $self.invoice_request.quantity = Some(quantity); Ok($return_value) @@ -249,13 +288,13 @@ macro_rules! invoice_request_builder_methods { ( /// Sets the [`InvoiceRequest::payer_note`]. /// /// Successive calls to this method will override the previous setting. - pub fn payer_note(mut $self: $self_type, payer_note: String) -> $return_type { + pub fn payer_note($($self_mut)* $self: $self_type, payer_note: String) -> $return_type { $self.invoice_request.payer_note = Some(payer_note); $return_value } - fn build_with_checks(mut $self: $self_type) -> Result< - (UnsignedInvoiceRequest, Option, Option<&'b Secp256k1>), + fn build_with_checks($($self_mut)* $self: $self_type) -> Result< + (UnsignedInvoiceRequest, Option, Option<&'b Secp256k1<$secp_context>>), Bolt12SemanticError > { #[cfg(feature = "std")] { @@ -285,8 +324,8 @@ macro_rules! invoice_request_builder_methods { ( Ok($self.build_without_checks()) } - fn build_without_checks(mut $self: $self_type) -> - (UnsignedInvoiceRequest, Option, Option<&'b Secp256k1>) + fn build_without_checks($($self_mut)* $self: $self_type) -> + (UnsignedInvoiceRequest, Option, Option<&'b Secp256k1<$secp_context>>) { // Create the metadata for stateless verification of a Bolt12Invoice. let mut keys = None; @@ -317,7 +356,10 @@ macro_rules! invoice_request_builder_methods { ( let payer_id = $self.payer_id.unwrap(); let invoice_request = InvoiceRequestContents { + #[cfg(not(c_bindings))] inner: $self.invoice_request, + #[cfg(c_bindings)] + inner: $self.invoice_request.clone(), payer_id, }; let unsigned_invoice_request = UnsignedInvoiceRequest::new($self.offer, invoice_request); @@ -328,29 +370,34 @@ macro_rules! invoice_request_builder_methods { ( #[cfg(test)] macro_rules! invoice_request_builder_test_methods { ( - $self: ident, $self_type: ty, $return_type: ty, $return_value: expr + $self: ident, $self_type: ty, $return_type: ty, $return_value: expr $(, $self_mut: tt)? ) => { - fn chain_unchecked(mut $self: $self_type, network: Network) -> $return_type { + #[cfg_attr(c_bindings, allow(dead_code))] + fn chain_unchecked($($self_mut)* $self: $self_type, network: Network) -> $return_type { let chain = ChainHash::using_genesis_block(network); $self.invoice_request.chain = Some(chain); $return_value } - fn amount_msats_unchecked(mut $self: $self_type, amount_msats: u64) -> $return_type { + #[cfg_attr(c_bindings, allow(dead_code))] + fn amount_msats_unchecked($($self_mut)* $self: $self_type, amount_msats: u64) -> $return_type { $self.invoice_request.amount_msats = Some(amount_msats); $return_value } - fn features_unchecked(mut $self: $self_type, features: InvoiceRequestFeatures) -> $return_type { + #[cfg_attr(c_bindings, allow(dead_code))] + fn features_unchecked($($self_mut)* $self: $self_type, features: InvoiceRequestFeatures) -> $return_type { $self.invoice_request.features = features; $return_value } - fn quantity_unchecked(mut $self: $self_type, quantity: u64) -> $return_type { + #[cfg_attr(c_bindings, allow(dead_code))] + fn quantity_unchecked($($self_mut)* $self: $self_type, quantity: u64) -> $return_type { $self.invoice_request.quantity = Some(quantity); $return_value } + #[cfg_attr(c_bindings, allow(dead_code))] pub(super) fn build_unchecked($self: $self_type) -> UnsignedInvoiceRequest { $self.build_without_checks().0 } @@ -361,14 +408,68 @@ impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, ExplicitPayerI } impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, DerivedPayerId, T> { - invoice_request_derived_payer_id_builder_methods!(self, Self); + invoice_request_derived_payer_id_builder_methods!(self, Self, T); } impl<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, P, T> { - invoice_request_builder_methods!(self, Self, Self, self); + invoice_request_builder_methods!(self, Self, Self, self, T, mut); #[cfg(test)] - invoice_request_builder_test_methods!(self, Self, Self, self); + invoice_request_builder_test_methods!(self, Self, Self, self, mut); +} + +#[cfg(all(c_bindings, not(test)))] +impl<'a, 'b> InvoiceRequestWithExplicitPayerIdBuilder<'a, 'b> { + invoice_request_explicit_payer_id_builder_methods!(self, &mut Self); + invoice_request_builder_methods!(self, &mut Self, (), (), secp256k1::All); +} + +#[cfg(all(c_bindings, test))] +impl<'a, 'b> InvoiceRequestWithExplicitPayerIdBuilder<'a, 'b> { + invoice_request_explicit_payer_id_builder_methods!(self, &mut Self); + invoice_request_builder_methods!(self, &mut Self, &mut Self, self, secp256k1::All); + invoice_request_builder_test_methods!(self, &mut Self, &mut Self, self); +} + +#[cfg(all(c_bindings, not(test)))] +impl<'a, 'b> InvoiceRequestWithDerivedPayerIdBuilder<'a, 'b> { + invoice_request_derived_payer_id_builder_methods!(self, &mut Self, secp256k1::All); + invoice_request_builder_methods!(self, &mut Self, (), (), secp256k1::All); +} + +#[cfg(all(c_bindings, test))] +impl<'a, 'b> InvoiceRequestWithDerivedPayerIdBuilder<'a, 'b> { + invoice_request_derived_payer_id_builder_methods!(self, &mut Self, secp256k1::All); + invoice_request_builder_methods!(self, &mut Self, &mut Self, self, secp256k1::All); + invoice_request_builder_test_methods!(self, &mut Self, &mut Self, self); +} + +#[cfg(c_bindings)] +impl<'a, 'b> From> +for InvoiceRequestBuilder<'a, 'b, ExplicitPayerId, secp256k1::All> { + fn from(builder: InvoiceRequestWithExplicitPayerIdBuilder<'a, 'b>) -> Self { + let InvoiceRequestWithExplicitPayerIdBuilder { + offer, invoice_request, payer_id, payer_id_strategy, secp_ctx, + } = builder; + + Self { + offer, invoice_request, payer_id, payer_id_strategy, secp_ctx, + } + } +} + +#[cfg(c_bindings)] +impl<'a, 'b> From> +for InvoiceRequestBuilder<'a, 'b, DerivedPayerId, secp256k1::All> { + fn from(builder: InvoiceRequestWithDerivedPayerIdBuilder<'a, 'b>) -> Self { + let InvoiceRequestWithDerivedPayerIdBuilder { + offer, invoice_request, payer_id, payer_id_strategy, secp_ctx, + } = builder; + + Self { + offer, invoice_request, payer_id, payer_id_strategy, secp_ctx, + } + } } /// A semantically valid [`InvoiceRequest`] that hasn't been signed. @@ -406,13 +507,15 @@ impl UnsignedInvoiceRequest { } } -macro_rules! unsigned_invoice_request_sign_method { ($self: ident, $self_type: ty) => { +macro_rules! unsigned_invoice_request_sign_method { ( + $self: ident, $self_type: ty $(, $self_mut: tt)? +) => { /// Signs the [`TaggedHash`] of the invoice request using the given function. /// /// Note: The hash computation may have included unknown, odd TLV records. /// /// This is not exported to bindings users as functions are not yet mapped. - pub fn sign(mut $self: $self_type, sign: F) -> Result> + pub fn sign($($self_mut)* $self: $self_type, sign: F) -> Result> where F: FnOnce(&Self) -> Result { @@ -426,15 +529,27 @@ macro_rules! unsigned_invoice_request_sign_method { ($self: ident, $self_type: t signature_tlv_stream.write(&mut $self.bytes).unwrap(); Ok(InvoiceRequest { + #[cfg(not(c_bindings))] bytes: $self.bytes, + #[cfg(c_bindings)] + bytes: $self.bytes.clone(), + #[cfg(not(c_bindings))] contents: $self.contents, + #[cfg(c_bindings)] + contents: $self.contents.clone(), signature, }) } } } +#[cfg(not(c_bindings))] +impl UnsignedInvoiceRequest { + unsigned_invoice_request_sign_method!(self, Self, mut); +} + +#[cfg(c_bindings)] impl UnsignedInvoiceRequest { - unsigned_invoice_request_sign_method!(self, Self); + unsigned_invoice_request_sign_method!(self, &mut Self); } impl AsRef for UnsignedInvoiceRequest { @@ -984,6 +1099,8 @@ mod tests { .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() .build().unwrap(); + #[cfg(c_bindings)] + let mut unsigned_invoice_request = unsigned_invoice_request; let mut buffer = Vec::new(); unsigned_invoice_request.write(&mut buffer).unwrap(); diff --git a/lightning/src/offers/offer.rs b/lightning/src/offers/offer.rs index be15272761a..8f6cac75d60 100644 --- a/lightning/src/offers/offer.rs +++ b/lightning/src/offers/offer.rs @@ -91,13 +91,21 @@ 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::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; +#[cfg(not(c_bindings))] +use { + crate::offers::invoice_request::{DerivedPayerId, ExplicitPayerId, InvoiceRequestBuilder}, +}; +#[cfg(c_bindings)] +use { + crate::offers::invoice_request::{InvoiceRequestWithDerivedPayerIdBuilder, InvoiceRequestWithExplicitPayerIdBuilder}, +}; + use crate::prelude::*; #[cfg(feature = "std")] @@ -598,14 +606,20 @@ macro_rules! request_invoice_derived_payer_id { ($self: ident, $builder: ty) => /// /// 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, + 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, + #[cfg(c_bindings)] + secp_ctx: &'b Secp256k1, payment_id: PaymentId ) -> Result<$builder, Bolt12SemanticError> where @@ -625,8 +639,6 @@ macro_rules! request_invoice_explicit_payer_id { ($self: ident, $builder: ty) => /// /// 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( &$self, payer_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES, @@ -654,8 +666,6 @@ macro_rules! request_invoice_explicit_payer_id { ($self: ident, $builder: ty) => /// /// 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, payer_id: PublicKey @@ -668,11 +678,18 @@ macro_rules! request_invoice_explicit_payer_id { ($self: ident, $builder: ty) => } } } +#[cfg(not(c_bindings))] impl Offer { request_invoice_derived_payer_id!(self, InvoiceRequestBuilder<'a, 'b, DerivedPayerId, T>); request_invoice_explicit_payer_id!(self, InvoiceRequestBuilder); } +#[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 { From 3a92c7b08a96167355da85f4daac76cd1251684e Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Sun, 25 Feb 2024 11:32:02 -0600 Subject: [PATCH 08/13] Merge similar InvoiceBuilder impl blocks This avoids needing to create additional macros when adding c_bindings support. --- lightning/src/offers/invoice.rs | 100 +++++++++++++++----------------- 1 file changed, 48 insertions(+), 52 deletions(-) diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index da88be15458..9e5922692ba 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -200,6 +200,25 @@ impl<'a> InvoiceBuilder<'a, ExplicitSigningPubkey> { Self::new(&refund.bytes, contents, ExplicitSigningPubkey {}) } + + /// Builds an unsigned [`Bolt12Invoice`] after checking for valid semantics. It can be signed by + /// [`UnsignedBolt12Invoice::sign`]. + pub fn build(self) -> Result { + #[cfg(feature = "std")] { + if self.invoice.is_offer_or_refund_expired() { + return Err(Bolt12SemanticError::AlreadyExpired); + } + } + + #[cfg(not(feature = "std"))] { + if self.invoice.is_offer_or_refund_expired_no_std(self.invoice.created_at()) { + return Err(Bolt12SemanticError::AlreadyExpired); + } + } + + let InvoiceBuilder { invreq_bytes, invoice, .. } = self; + Ok(UnsignedBolt12Invoice::new(invreq_bytes, invoice)) + } } impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> { @@ -234,6 +253,35 @@ impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> { Self::new(&refund.bytes, contents, DerivedSigningPubkey(keys)) } + + /// Builds a signed [`Bolt12Invoice`] after checking for valid semantics. + pub fn build_and_sign( + self, secp_ctx: &Secp256k1 + ) -> Result { + #[cfg(feature = "std")] { + if self.invoice.is_offer_or_refund_expired() { + return Err(Bolt12SemanticError::AlreadyExpired); + } + } + + #[cfg(not(feature = "std"))] { + if self.invoice.is_offer_or_refund_expired_no_std(self.invoice.created_at()) { + return Err(Bolt12SemanticError::AlreadyExpired); + } + } + + let InvoiceBuilder { + invreq_bytes, invoice, signing_pubkey_strategy: DerivedSigningPubkey(keys) + } = self; + let unsigned_invoice = UnsignedBolt12Invoice::new(invreq_bytes, invoice); + + let invoice = unsigned_invoice + .sign::<_, Infallible>( + |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) + ) + .unwrap(); + Ok(invoice) + } } impl<'a, S: SigningPubkeyStrategy> InvoiceBuilder<'a, S> { @@ -331,58 +379,6 @@ impl<'a, S: SigningPubkeyStrategy> InvoiceBuilder<'a, S> { } } -impl<'a> InvoiceBuilder<'a, ExplicitSigningPubkey> { - /// Builds an unsigned [`Bolt12Invoice`] after checking for valid semantics. It can be signed by - /// [`UnsignedBolt12Invoice::sign`]. - pub fn build(self) -> Result { - #[cfg(feature = "std")] { - if self.invoice.is_offer_or_refund_expired() { - return Err(Bolt12SemanticError::AlreadyExpired); - } - } - - #[cfg(not(feature = "std"))] { - if self.invoice.is_offer_or_refund_expired_no_std(self.invoice.created_at()) { - return Err(Bolt12SemanticError::AlreadyExpired); - } - } - - let InvoiceBuilder { invreq_bytes, invoice, .. } = self; - Ok(UnsignedBolt12Invoice::new(invreq_bytes, invoice)) - } -} - -impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> { - /// Builds a signed [`Bolt12Invoice`] after checking for valid semantics. - pub fn build_and_sign( - self, secp_ctx: &Secp256k1 - ) -> Result { - #[cfg(feature = "std")] { - if self.invoice.is_offer_or_refund_expired() { - return Err(Bolt12SemanticError::AlreadyExpired); - } - } - - #[cfg(not(feature = "std"))] { - if self.invoice.is_offer_or_refund_expired_no_std(self.invoice.created_at()) { - return Err(Bolt12SemanticError::AlreadyExpired); - } - } - - let InvoiceBuilder { - invreq_bytes, invoice, signing_pubkey_strategy: DerivedSigningPubkey(keys) - } = self; - let unsigned_invoice = UnsignedBolt12Invoice::new(invreq_bytes, invoice); - - let invoice = unsigned_invoice - .sign::<_, Infallible>( - |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) - ) - .unwrap(); - Ok(invoice) - } -} - /// A semantically valid [`Bolt12Invoice`] that hasn't been signed. /// /// # Serialization From 07d628e0faf1a953a660144e3da77b0542027f84 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Sun, 25 Feb 2024 14:58:07 -0600 Subject: [PATCH 09/13] Macro-ize InvoiceBuilder InvoiceBuilder is not exported to bindings because it has methods that take `self` by value and are only implemented for certain type parameterizations. Define these methods using macros such that different builders and related methods can be defined for c_bindings. --- lightning/src/offers/invoice.rs | 90 +++++++++++-------- lightning/src/offers/invoice_request.rs | 110 +++++++++++------------- lightning/src/offers/refund.rs | 41 +++++---- 3 files changed, 130 insertions(+), 111 deletions(-) diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index 9e5922692ba..457f4ac3038 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -169,7 +169,7 @@ pub struct DerivedSigningPubkey(KeyPair); impl SigningPubkeyStrategy for ExplicitSigningPubkey {} impl SigningPubkeyStrategy for DerivedSigningPubkey {} -impl<'a> InvoiceBuilder<'a, ExplicitSigningPubkey> { +macro_rules! invoice_explicit_signing_pubkey_builder_methods { ($self: ident, $self_type: ty) => { pub(super) fn for_offer( invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, created_at: Duration, payment_hash: PaymentHash @@ -203,25 +203,25 @@ impl<'a> InvoiceBuilder<'a, ExplicitSigningPubkey> { /// Builds an unsigned [`Bolt12Invoice`] after checking for valid semantics. It can be signed by /// [`UnsignedBolt12Invoice::sign`]. - pub fn build(self) -> Result { + pub fn build($self: $self_type) -> Result { #[cfg(feature = "std")] { - if self.invoice.is_offer_or_refund_expired() { + if $self.invoice.is_offer_or_refund_expired() { return Err(Bolt12SemanticError::AlreadyExpired); } } #[cfg(not(feature = "std"))] { - if self.invoice.is_offer_or_refund_expired_no_std(self.invoice.created_at()) { + if $self.invoice.is_offer_or_refund_expired_no_std($self.invoice.created_at()) { return Err(Bolt12SemanticError::AlreadyExpired); } } - let InvoiceBuilder { invreq_bytes, invoice, .. } = self; + let InvoiceBuilder { invreq_bytes, invoice, .. } = $self; Ok(UnsignedBolt12Invoice::new(invreq_bytes, invoice)) } -} +} } -impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> { +macro_rules! invoice_derived_signing_pubkey_builder_methods { ($self: ident, $self_type: ty) => { pub(super) fn for_offer_using_keys( invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, created_at: Duration, payment_hash: PaymentHash, keys: KeyPair @@ -256,23 +256,23 @@ impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> { /// Builds a signed [`Bolt12Invoice`] after checking for valid semantics. pub fn build_and_sign( - self, secp_ctx: &Secp256k1 + $self: $self_type, secp_ctx: &Secp256k1 ) -> Result { #[cfg(feature = "std")] { - if self.invoice.is_offer_or_refund_expired() { + if $self.invoice.is_offer_or_refund_expired() { return Err(Bolt12SemanticError::AlreadyExpired); } } #[cfg(not(feature = "std"))] { - if self.invoice.is_offer_or_refund_expired_no_std(self.invoice.created_at()) { + if $self.invoice.is_offer_or_refund_expired_no_std($self.invoice.created_at()) { return Err(Bolt12SemanticError::AlreadyExpired); } } let InvoiceBuilder { invreq_bytes, invoice, signing_pubkey_strategy: DerivedSigningPubkey(keys) - } = self; + } = $self; let unsigned_invoice = UnsignedBolt12Invoice::new(invreq_bytes, invoice); let invoice = unsigned_invoice @@ -282,9 +282,11 @@ impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> { .unwrap(); Ok(invoice) } -} +} } -impl<'a, S: SigningPubkeyStrategy> InvoiceBuilder<'a, S> { +macro_rules! invoice_builder_methods { ( + $self: ident, $self_type: ty, $return_type: ty, $return_value: expr +) => { pub(crate) fn amount_msats( invoice_request: &InvoiceRequest ) -> Result { @@ -326,57 +328,69 @@ impl<'a, S: SigningPubkeyStrategy> InvoiceBuilder<'a, S> { /// [`Bolt12Invoice::is_expired`]. /// /// Successive calls to this method will override the previous setting. - pub fn relative_expiry(mut self, relative_expiry_secs: u32) -> Self { + pub fn relative_expiry(mut $self: $self_type, relative_expiry_secs: u32) -> $return_type { let relative_expiry = Duration::from_secs(relative_expiry_secs as u64); - self.invoice.fields_mut().relative_expiry = Some(relative_expiry); - self + $self.invoice.fields_mut().relative_expiry = Some(relative_expiry); + $return_value } /// Adds a P2WSH address to [`Bolt12Invoice::fallbacks`]. /// /// Successive calls to this method will add another address. Caller is responsible for not /// adding duplicate addresses and only calling if capable of receiving to P2WSH addresses. - pub fn fallback_v0_p2wsh(mut self, script_hash: &WScriptHash) -> Self { + pub fn fallback_v0_p2wsh(mut $self: $self_type, script_hash: &WScriptHash) -> $return_type { let address = FallbackAddress { version: WitnessVersion::V0.to_num(), program: Vec::from(script_hash.to_byte_array()), }; - self.invoice.fields_mut().fallbacks.get_or_insert_with(Vec::new).push(address); - self + $self.invoice.fields_mut().fallbacks.get_or_insert_with(Vec::new).push(address); + $return_value } /// Adds a P2WPKH address to [`Bolt12Invoice::fallbacks`]. /// /// Successive calls to this method will add another address. Caller is responsible for not /// adding duplicate addresses and only calling if capable of receiving to P2WPKH addresses. - pub fn fallback_v0_p2wpkh(mut self, pubkey_hash: &WPubkeyHash) -> Self { + pub fn fallback_v0_p2wpkh(mut $self: $self_type, pubkey_hash: &WPubkeyHash) -> $return_type { let address = FallbackAddress { version: WitnessVersion::V0.to_num(), program: Vec::from(pubkey_hash.to_byte_array()), }; - self.invoice.fields_mut().fallbacks.get_or_insert_with(Vec::new).push(address); - self + $self.invoice.fields_mut().fallbacks.get_or_insert_with(Vec::new).push(address); + $return_value } /// Adds a P2TR address to [`Bolt12Invoice::fallbacks`]. /// /// Successive calls to this method will add another address. Caller is responsible for not /// adding duplicate addresses and only calling if capable of receiving to P2TR addresses. - pub fn fallback_v1_p2tr_tweaked(mut self, output_key: &TweakedPublicKey) -> Self { + pub fn fallback_v1_p2tr_tweaked(mut $self: $self_type, output_key: &TweakedPublicKey) -> $return_type { let address = FallbackAddress { version: WitnessVersion::V1.to_num(), program: Vec::from(&output_key.serialize()[..]), }; - self.invoice.fields_mut().fallbacks.get_or_insert_with(Vec::new).push(address); - self + $self.invoice.fields_mut().fallbacks.get_or_insert_with(Vec::new).push(address); + $return_value } /// Sets [`Bolt12Invoice::invoice_features`] to indicate MPP may be used. Otherwise, MPP is /// disallowed. - pub fn allow_mpp(mut self) -> Self { - self.invoice.fields_mut().features.set_basic_mpp_optional(); - self + pub fn allow_mpp(mut $self: $self_type) -> $return_type { + $self.invoice.fields_mut().features.set_basic_mpp_optional(); + $return_value } +} } + +impl<'a> InvoiceBuilder<'a, ExplicitSigningPubkey> { + invoice_explicit_signing_pubkey_builder_methods!(self, Self); +} + +impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> { + invoice_derived_signing_pubkey_builder_methods!(self, Self); +} + +impl<'a, S: SigningPubkeyStrategy> InvoiceBuilder<'a, S> { + invoice_builder_methods!(self, Self, Self, self); } /// A semantically valid [`Bolt12Invoice`] that hasn't been signed. @@ -412,32 +426,38 @@ impl UnsignedBolt12Invoice { pub fn tagged_hash(&self) -> &TaggedHash { &self.tagged_hash } +} +macro_rules! unsigned_invoice_sign_method { ($self: ident, $self_type: ty) => { /// Signs the [`TaggedHash`] of the invoice using the given function. /// /// Note: The hash computation may have included unknown, odd TLV records. /// /// This is not exported to bindings users as functions aren't currently mapped. - pub fn sign(mut self, sign: F) -> Result> + pub fn sign(mut $self: $self_type, sign: F) -> Result> where F: FnOnce(&Self) -> Result { - let pubkey = self.contents.fields().signing_pubkey; - let signature = merkle::sign_message(sign, &self, pubkey)?; + let pubkey = $self.contents.fields().signing_pubkey; + let signature = merkle::sign_message(sign, &$self, pubkey)?; // Append the signature TLV record to the bytes. let signature_tlv_stream = SignatureTlvStreamRef { signature: Some(&signature), }; - signature_tlv_stream.write(&mut self.bytes).unwrap(); + signature_tlv_stream.write(&mut $self.bytes).unwrap(); Ok(Bolt12Invoice { - bytes: self.bytes, - contents: self.contents, + bytes: $self.bytes, + contents: $self.contents, signature, - tagged_hash: self.tagged_hash, + tagged_hash: $self.tagged_hash, }) } +} } + +impl UnsignedBolt12Invoice { + unsigned_invoice_sign_method!(self, Self); } impl AsRef for UnsignedBolt12Invoice { diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index 792214d5250..967db3cdd88 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -662,17 +662,9 @@ impl UnsignedInvoiceRequest { invoice_request_accessors!(self, self.contents); } -impl InvoiceRequest { - offer_accessors!(self, self.contents.inner.offer); - invoice_request_accessors!(self, self.contents); - - /// Signature of the invoice request using [`payer_id`]. - /// - /// [`payer_id`]: Self::payer_id - pub fn signature(&self) -> Signature { - self.signature - } - +macro_rules! invoice_request_respond_with_explicit_signing_pubkey_methods { ( + $self: ident, $contents: expr, $builder: ty +) => { /// Creates an [`InvoiceBuilder`] for the request with the given required fields and using the /// [`Duration`] since [`std::time::SystemTime::UNIX_EPOCH`] as the creation time. /// @@ -684,13 +676,13 @@ impl InvoiceRequest { /// [`Duration`]: core::time::Duration #[cfg(feature = "std")] pub fn respond_with( - &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash - ) -> Result, Bolt12SemanticError> { + &$self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash + ) -> Result<$builder, Bolt12SemanticError> { let created_at = std::time::SystemTime::now() .duration_since(std::time::SystemTime::UNIX_EPOCH) .expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH"); - self.respond_with_no_std(payment_paths, payment_hash, created_at) + $contents.respond_with_no_std(payment_paths, payment_hash, created_at) } /// Creates an [`InvoiceBuilder`] for the request with the given required fields. @@ -719,31 +711,48 @@ impl InvoiceRequest { /// [`Bolt12Invoice::created_at`]: crate::offers::invoice::Bolt12Invoice::created_at /// [`OfferBuilder::deriving_signing_pubkey`]: crate::offers::offer::OfferBuilder::deriving_signing_pubkey pub fn respond_with_no_std( - &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash, + &$self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash, created_at: core::time::Duration - ) -> Result, Bolt12SemanticError> { - if self.invoice_request_features().requires_unknown_bits() { + ) -> Result<$builder, Bolt12SemanticError> { + if $contents.invoice_request_features().requires_unknown_bits() { return Err(Bolt12SemanticError::UnknownRequiredFeatures); } - InvoiceBuilder::for_offer(self, payment_paths, created_at, payment_hash) + <$builder>::for_offer(&$contents, payment_paths, created_at, payment_hash) } +} } +macro_rules! invoice_request_verify_method { ($self: ident, $self_type: ty) => { /// Verifies that the request was for an offer created using the given key. Returns the verified /// request which contains the derived keys needed to sign a [`Bolt12Invoice`] for the request /// if they could be extracted from the metadata. /// /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice pub fn verify( - self, key: &ExpandedKey, secp_ctx: &Secp256k1 + $self: $self_type, key: &ExpandedKey, secp_ctx: &Secp256k1 ) -> Result { - let keys = self.contents.inner.offer.verify(&self.bytes, key, secp_ctx)?; + let keys = $self.contents.inner.offer.verify(&$self.bytes, key, secp_ctx)?; Ok(VerifiedInvoiceRequest { - inner: self, + inner: $self, keys, }) } +} } + +impl InvoiceRequest { + offer_accessors!(self, self.contents.inner.offer); + invoice_request_accessors!(self, self.contents); + invoice_request_respond_with_explicit_signing_pubkey_methods!(self, self, InvoiceBuilder); + invoice_request_verify_method!(self, Self); + + /// Signature of the invoice request using [`payer_id`]. + /// + /// [`payer_id`]: Self::payer_id + pub fn signature(&self) -> Signature { + self.signature + } + pub(crate) fn as_tlv_stream(&self) -> FullInvoiceRequestTlvStreamRef { let (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream) = self.contents.as_tlv_stream(); @@ -754,37 +763,9 @@ impl InvoiceRequest { } } -impl VerifiedInvoiceRequest { - offer_accessors!(self, self.inner.contents.inner.offer); - invoice_request_accessors!(self, self.inner.contents); - - /// Creates an [`InvoiceBuilder`] for the request with the given required fields and using the - /// [`Duration`] since [`std::time::SystemTime::UNIX_EPOCH`] as the creation time. - /// - /// See [`InvoiceRequest::respond_with_no_std`] for further details. - /// - /// This is not exported to bindings users as builder patterns don't map outside of move semantics. - /// - /// [`Duration`]: core::time::Duration - #[cfg(feature = "std")] - pub fn respond_with( - &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash - ) -> Result, Bolt12SemanticError> { - self.inner.respond_with(payment_paths, payment_hash) - } - - /// Creates an [`InvoiceBuilder`] for the request with the given required fields. - /// - /// See [`InvoiceRequest::respond_with_no_std`] for further details. - /// - /// This is not exported to bindings users as builder patterns don't map outside of move semantics. - pub fn respond_with_no_std( - &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash, - created_at: core::time::Duration - ) -> Result, Bolt12SemanticError> { - self.inner.respond_with_no_std(payment_paths, payment_hash, created_at) - } - +macro_rules! invoice_request_respond_with_derived_signing_pubkey_methods { ( + $self: ident, $contents: expr, $builder: ty +) => { /// Creates an [`InvoiceBuilder`] for the request using the given required fields and that uses /// derived signing keys from the originating [`Offer`] to sign the [`Bolt12Invoice`]. Must use /// the same [`ExpandedKey`] as the one used to create the offer. @@ -796,13 +777,13 @@ impl VerifiedInvoiceRequest { /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice #[cfg(feature = "std")] pub fn respond_using_derived_keys( - &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash - ) -> Result, Bolt12SemanticError> { + &$self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash + ) -> Result<$builder, Bolt12SemanticError> { let created_at = std::time::SystemTime::now() .duration_since(std::time::SystemTime::UNIX_EPOCH) .expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH"); - self.respond_using_derived_keys_no_std(payment_paths, payment_hash, created_at) + $self.respond_using_derived_keys_no_std(payment_paths, payment_hash, created_at) } /// Creates an [`InvoiceBuilder`] for the request using the given required fields and that uses @@ -815,22 +796,29 @@ impl VerifiedInvoiceRequest { /// /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice pub fn respond_using_derived_keys_no_std( - &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash, + &$self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash, created_at: core::time::Duration - ) -> Result, Bolt12SemanticError> { - if self.inner.invoice_request_features().requires_unknown_bits() { + ) -> Result<$builder, Bolt12SemanticError> { + if $self.inner.invoice_request_features().requires_unknown_bits() { return Err(Bolt12SemanticError::UnknownRequiredFeatures); } - let keys = match self.keys { + let keys = match $self.keys { None => return Err(Bolt12SemanticError::InvalidMetadata), Some(keys) => keys, }; - InvoiceBuilder::for_offer_using_keys( - &self.inner, payment_paths, created_at, payment_hash, keys + <$builder>::for_offer_using_keys( + &$self.inner, payment_paths, created_at, payment_hash, keys ) } +} } + +impl VerifiedInvoiceRequest { + offer_accessors!(self, self.inner.contents.inner.offer); + invoice_request_accessors!(self, self.inner.contents); + invoice_request_respond_with_explicit_signing_pubkey_methods!(self, self.inner, InvoiceBuilder); + invoice_request_respond_with_derived_signing_pubkey_methods!(self, self.inner, InvoiceBuilder); } impl InvoiceRequestContents { diff --git a/lightning/src/offers/refund.rs b/lightning/src/offers/refund.rs index f4e9c4a8cc7..e06f045d309 100644 --- a/lightning/src/offers/refund.rs +++ b/lightning/src/offers/refund.rs @@ -480,7 +480,9 @@ impl Refund { pub fn payer_note(&self) -> Option { self.contents.payer_note() } +} +macro_rules! respond_with_explicit_signing_pubkey_methods { ($self: ident, $builder: ty) => { /// Creates an [`InvoiceBuilder`] for the refund with the given required fields and using the /// [`Duration`] since [`std::time::SystemTime::UNIX_EPOCH`] as the creation time. /// @@ -492,14 +494,14 @@ impl Refund { /// [`Duration`]: core::time::Duration #[cfg(feature = "std")] pub fn respond_with( - &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash, + &$self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash, signing_pubkey: PublicKey, - ) -> Result, Bolt12SemanticError> { + ) -> Result<$builder, Bolt12SemanticError> { let created_at = std::time::SystemTime::now() .duration_since(std::time::SystemTime::UNIX_EPOCH) .expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH"); - self.respond_with_no_std(payment_paths, payment_hash, signing_pubkey, created_at) + $self.respond_with_no_std(payment_paths, payment_hash, signing_pubkey, created_at) } /// Creates an [`InvoiceBuilder`] for the refund with the given required fields. @@ -525,16 +527,18 @@ impl Refund { /// /// [`Bolt12Invoice::created_at`]: crate::offers::invoice::Bolt12Invoice::created_at pub fn respond_with_no_std( - &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash, + &$self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash, signing_pubkey: PublicKey, created_at: Duration - ) -> Result, Bolt12SemanticError> { - if self.features().requires_unknown_bits() { + ) -> Result<$builder, Bolt12SemanticError> { + if $self.features().requires_unknown_bits() { return Err(Bolt12SemanticError::UnknownRequiredFeatures); } - InvoiceBuilder::for_refund(self, payment_paths, created_at, payment_hash, signing_pubkey) + <$builder>::for_refund($self, payment_paths, created_at, payment_hash, signing_pubkey) } +} } +macro_rules! respond_with_derived_signing_pubkey_methods { ($self: ident, $builder: ty) => { /// Creates an [`InvoiceBuilder`] for the refund using the given required fields and that uses /// derived signing keys to sign the [`Bolt12Invoice`]. /// @@ -545,9 +549,9 @@ impl Refund { /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice #[cfg(feature = "std")] pub fn respond_using_derived_keys( - &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash, + &$self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash, expanded_key: &ExpandedKey, entropy_source: ES - ) -> Result, Bolt12SemanticError> + ) -> Result<$builder, Bolt12SemanticError> where ES::Target: EntropySource, { @@ -555,7 +559,7 @@ impl Refund { .duration_since(std::time::SystemTime::UNIX_EPOCH) .expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH"); - self.respond_using_derived_keys_no_std( + $self.respond_using_derived_keys_no_std( payment_paths, payment_hash, created_at, expanded_key, entropy_source ) } @@ -569,22 +573,29 @@ impl Refund { /// /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice pub fn respond_using_derived_keys_no_std( - &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash, + &$self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash, created_at: core::time::Duration, expanded_key: &ExpandedKey, entropy_source: ES - ) -> Result, Bolt12SemanticError> + ) -> Result<$builder, Bolt12SemanticError> where ES::Target: EntropySource, { - if self.features().requires_unknown_bits() { + if $self.features().requires_unknown_bits() { return Err(Bolt12SemanticError::UnknownRequiredFeatures); } let nonce = Nonce::from_entropy_source(entropy_source); let keys = signer::derive_keys(nonce, expanded_key); - InvoiceBuilder::for_refund_using_keys(self, payment_paths, created_at, payment_hash, keys) + <$builder>::for_refund_using_keys($self, payment_paths, created_at, payment_hash, keys) } +} } - #[cfg(test)] +impl Refund { + respond_with_explicit_signing_pubkey_methods!(self, InvoiceBuilder); + respond_with_derived_signing_pubkey_methods!(self, InvoiceBuilder); +} + +#[cfg(test)] +impl Refund { fn as_tlv_stream(&self) -> RefundTlvStreamRef { self.contents.as_tlv_stream() } From 3bd00b943a3f3a101a77ad127b219dad91845aa6 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Sun, 25 Feb 2024 20:42:57 -0600 Subject: [PATCH 10/13] Add c_bindings version of InvoiceBuilder Use the macros introduced in the previous commit to define two builders for each type parameterization of InvoiceBuilder - InvoiceWithExplicitSigningPubkeyBuilder - InvoiceWithDerivedSigningPubkeyBuilder The difference between these and InvoiceBuilder is that these have methods that take `self` by mutable reference instead of by value and don't return anything instead returning the modified builder. This is required because bindings don't support move semantics nor impl blocks specific to a certain type parameterization. Because of this, the builder's contents must be cloned when building a Bolt12Invoice. Keeps InvoiceBuilder defined so that it can be used internally in ChannelManager's OffersMessageHandler even when compiled for c_bindings. --- lightning/src/ln/channelmanager.rs | 14 ++- lightning/src/offers/invoice.rs | 158 +++++++++++++++++++++--- lightning/src/offers/invoice_request.rs | 50 ++++++-- lightning/src/offers/refund.rs | 18 ++- 4 files changed, 209 insertions(+), 31 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 87428ca36aa..a56793fa16d 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -57,7 +57,7 @@ use crate::ln::msgs::{ChannelMessageHandler, DecodeError, LightningError}; use crate::ln::outbound_payment; use crate::ln::outbound_payment::{Bolt12PaymentError, OutboundPayments, PaymentAttempts, PendingOutboundPayment, SendAlongPathArgs, StaleExpiration}; use crate::ln::wire::Encode; -use crate::offers::invoice::{BlindedPayInfo, Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, DerivedSigningPubkey, InvoiceBuilder}; +use crate::offers::invoice::{BlindedPayInfo, Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder}; use crate::offers::invoice_error::InvoiceError; use crate::offers::invoice_request::{DerivedPayerId, InvoiceRequestBuilder}; use crate::offers::merkle::SignError; @@ -7834,6 +7834,7 @@ where let builder = refund.respond_using_derived_keys_no_std( payment_paths, payment_hash, created_at, expanded_key, entropy )?; + let builder: InvoiceBuilder = builder.into(); let invoice = builder.allow_mpp().build_and_sign(secp_ctx)?; let reply_path = self.create_blinded_path() .map_err(|_| Bolt12SemanticError::MissingPaths)?; @@ -9281,6 +9282,8 @@ where let builder = invoice_request.respond_using_derived_keys_no_std( payment_paths, payment_hash, created_at ); + let builder: Result, _> = + builder.map(|b| b.into()); match builder.and_then(|b| b.allow_mpp().build_and_sign(secp_ctx)) { Ok(invoice) => Some(OffersMessage::Invoice(invoice)), Err(error) => Some(OffersMessage::InvoiceError(error.into())), @@ -9292,9 +9295,13 @@ where let builder = invoice_request.respond_with_no_std( payment_paths, payment_hash, created_at ); + let builder: Result, _> = + builder.map(|b| b.into()); let response = builder.and_then(|builder| builder.allow_mpp().build()) .map_err(|e| OffersMessage::InvoiceError(e.into())) - .and_then(|invoice| + .and_then(|invoice| { + #[cfg(c_bindings)] + let mut invoice = invoice; match invoice.sign(|invoice| self.node_signer.sign_bolt12_invoice(invoice)) { Ok(invoice) => Ok(OffersMessage::Invoice(invoice)), Err(SignError::Signing(())) => Err(OffersMessage::InvoiceError( @@ -9303,7 +9310,8 @@ where Err(SignError::Verification(_)) => Err(OffersMessage::InvoiceError( InvoiceError::from_string("Failed invoice signature verification".to_string()) )), - }); + } + }); match response { Ok(invoice) => Some(invoice), Err(error) => Some(error), diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index 457f4ac3038..f665bd3b26e 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -28,7 +28,7 @@ //! use lightning::util::ser::Writeable; //! //! # use lightning::ln::PaymentHash; -//! # use lightning::offers::invoice::BlindedPayInfo; +//! # use lightning::offers::invoice::{BlindedPayInfo, ExplicitSigningPubkey, InvoiceBuilder}; //! # use lightning::blinded_path::BlindedPath; //! # //! # fn create_payment_paths() -> Vec<(BlindedPayInfo, BlindedPath)> { unimplemented!() } @@ -44,6 +44,7 @@ //! let mut buffer = Vec::new(); //! //! // Invoice for the "offer to be paid" flow. +//! # >::from( //! InvoiceRequest::try_from(bytes)? #![cfg_attr(feature = "std", doc = " .respond_with(payment_paths, payment_hash)? @@ -51,6 +52,7 @@ #![cfg_attr(not(feature = "std"), doc = " .respond_with_no_std(payment_paths, payment_hash, core::time::Duration::from_secs(0))? ")] +//! # ) //! .relative_expiry(3600) //! .allow_mpp() //! .fallback_v0_p2wpkh(&wpubkey_hash) @@ -74,6 +76,7 @@ //! # let mut buffer = Vec::new(); //! //! // Invoice for the "offer for money" flow. +//! # >::from( //! "lnr1qcp4256ypq" //! .parse::()? #![cfg_attr(feature = "std", doc = " @@ -82,6 +85,7 @@ #![cfg_attr(not(feature = "std"), doc = " .respond_with_no_std(payment_paths, payment_hash, pubkey, core::time::Duration::from_secs(0))? ")] +//! # ) //! .relative_expiry(3600) //! .allow_mpp() //! .fallback_v0_p2wpkh(&wpubkey_hash) @@ -151,6 +155,38 @@ pub struct InvoiceBuilder<'a, S: SigningPubkeyStrategy> { signing_pubkey_strategy: S, } +/// Builds a [`Bolt12Invoice`] from either: +/// - an [`InvoiceRequest`] for the "offer to be paid" flow or +/// - a [`Refund`] for the "offer for money" flow. +/// +/// See [module-level documentation] for usage. +/// +/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest +/// [`Refund`]: crate::offers::refund::Refund +/// [module-level documentation]: self +#[cfg(c_bindings)] +pub struct InvoiceWithExplicitSigningPubkeyBuilder<'a> { + invreq_bytes: &'a Vec, + invoice: InvoiceContents, + signing_pubkey_strategy: ExplicitSigningPubkey, +} + +/// Builds a [`Bolt12Invoice`] from either: +/// - an [`InvoiceRequest`] for the "offer to be paid" flow or +/// - a [`Refund`] for the "offer for money" flow. +/// +/// See [module-level documentation] for usage. +/// +/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest +/// [`Refund`]: crate::offers::refund::Refund +/// [module-level documentation]: self +#[cfg(c_bindings)] +pub struct InvoiceWithDerivedSigningPubkeyBuilder<'a> { + invreq_bytes: &'a Vec, + invoice: InvoiceContents, + signing_pubkey_strategy: DerivedSigningPubkey, +} + /// Indicates how [`Bolt12Invoice::signing_pubkey`] was set. /// /// This is not exported to bindings users as builder patterns don't map outside of move semantics. @@ -170,6 +206,7 @@ impl SigningPubkeyStrategy for ExplicitSigningPubkey {} impl SigningPubkeyStrategy for DerivedSigningPubkey {} macro_rules! invoice_explicit_signing_pubkey_builder_methods { ($self: ident, $self_type: ty) => { + #[cfg_attr(c_bindings, allow(dead_code))] pub(super) fn for_offer( invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, created_at: Duration, payment_hash: PaymentHash @@ -186,6 +223,7 @@ macro_rules! invoice_explicit_signing_pubkey_builder_methods { ($self: ident, $s Self::new(&invoice_request.bytes, contents, ExplicitSigningPubkey {}) } + #[cfg_attr(c_bindings, allow(dead_code))] pub(super) fn for_refund( refund: &'a Refund, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, created_at: Duration, payment_hash: PaymentHash, signing_pubkey: PublicKey @@ -216,12 +254,18 @@ macro_rules! invoice_explicit_signing_pubkey_builder_methods { ($self: ident, $s } } - let InvoiceBuilder { invreq_bytes, invoice, .. } = $self; - Ok(UnsignedBolt12Invoice::new(invreq_bytes, invoice)) + let Self { invreq_bytes, invoice, .. } = $self; + #[cfg(not(c_bindings))] { + Ok(UnsignedBolt12Invoice::new(invreq_bytes, invoice)) + } + #[cfg(c_bindings)] { + Ok(UnsignedBolt12Invoice::new(invreq_bytes, invoice.clone())) + } } } } macro_rules! invoice_derived_signing_pubkey_builder_methods { ($self: ident, $self_type: ty) => { + #[cfg_attr(c_bindings, allow(dead_code))] pub(super) fn for_offer_using_keys( invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, created_at: Duration, payment_hash: PaymentHash, keys: KeyPair @@ -238,6 +282,7 @@ macro_rules! invoice_derived_signing_pubkey_builder_methods { ($self: ident, $se Self::new(&invoice_request.bytes, contents, DerivedSigningPubkey(keys)) } + #[cfg_attr(c_bindings, allow(dead_code))] pub(super) fn for_refund_using_keys( refund: &'a Refund, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, created_at: Duration, payment_hash: PaymentHash, keys: KeyPair, @@ -270,10 +315,13 @@ macro_rules! invoice_derived_signing_pubkey_builder_methods { ($self: ident, $se } } - let InvoiceBuilder { + let Self { invreq_bytes, invoice, signing_pubkey_strategy: DerivedSigningPubkey(keys) } = $self; + #[cfg(not(c_bindings))] let unsigned_invoice = UnsignedBolt12Invoice::new(invreq_bytes, invoice); + #[cfg(c_bindings)] + let mut unsigned_invoice = UnsignedBolt12Invoice::new(invreq_bytes, invoice.clone()); let invoice = unsigned_invoice .sign::<_, Infallible>( @@ -285,7 +333,7 @@ macro_rules! invoice_derived_signing_pubkey_builder_methods { ($self: ident, $se } } macro_rules! invoice_builder_methods { ( - $self: ident, $self_type: ty, $return_type: ty, $return_value: expr + $self: ident, $self_type: ty, $return_type: ty, $return_value: expr, $type_param: ty $(, $self_mut: tt)? ) => { pub(crate) fn amount_msats( invoice_request: &InvoiceRequest @@ -303,6 +351,7 @@ macro_rules! invoice_builder_methods { ( } } + #[cfg_attr(c_bindings, allow(dead_code))] fn fields( payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, created_at: Duration, payment_hash: PaymentHash, amount_msats: u64, signing_pubkey: PublicKey @@ -313,8 +362,9 @@ macro_rules! invoice_builder_methods { ( } } + #[cfg_attr(c_bindings, allow(dead_code))] fn new( - invreq_bytes: &'a Vec, contents: InvoiceContents, signing_pubkey_strategy: S + invreq_bytes: &'a Vec, contents: InvoiceContents, signing_pubkey_strategy: $type_param ) -> Result { if contents.fields().payment_paths.is_empty() { return Err(Bolt12SemanticError::MissingPaths); @@ -328,7 +378,7 @@ macro_rules! invoice_builder_methods { ( /// [`Bolt12Invoice::is_expired`]. /// /// Successive calls to this method will override the previous setting. - pub fn relative_expiry(mut $self: $self_type, relative_expiry_secs: u32) -> $return_type { + pub fn relative_expiry($($self_mut)* $self: $self_type, relative_expiry_secs: u32) -> $return_type { let relative_expiry = Duration::from_secs(relative_expiry_secs as u64); $self.invoice.fields_mut().relative_expiry = Some(relative_expiry); $return_value @@ -338,7 +388,7 @@ macro_rules! invoice_builder_methods { ( /// /// Successive calls to this method will add another address. Caller is responsible for not /// adding duplicate addresses and only calling if capable of receiving to P2WSH addresses. - pub fn fallback_v0_p2wsh(mut $self: $self_type, script_hash: &WScriptHash) -> $return_type { + pub fn fallback_v0_p2wsh($($self_mut)* $self: $self_type, script_hash: &WScriptHash) -> $return_type { let address = FallbackAddress { version: WitnessVersion::V0.to_num(), program: Vec::from(script_hash.to_byte_array()), @@ -351,7 +401,7 @@ macro_rules! invoice_builder_methods { ( /// /// Successive calls to this method will add another address. Caller is responsible for not /// adding duplicate addresses and only calling if capable of receiving to P2WPKH addresses. - pub fn fallback_v0_p2wpkh(mut $self: $self_type, pubkey_hash: &WPubkeyHash) -> $return_type { + pub fn fallback_v0_p2wpkh($($self_mut)* $self: $self_type, pubkey_hash: &WPubkeyHash) -> $return_type { let address = FallbackAddress { version: WitnessVersion::V0.to_num(), program: Vec::from(pubkey_hash.to_byte_array()), @@ -364,7 +414,7 @@ macro_rules! invoice_builder_methods { ( /// /// Successive calls to this method will add another address. Caller is responsible for not /// adding duplicate addresses and only calling if capable of receiving to P2TR addresses. - pub fn fallback_v1_p2tr_tweaked(mut $self: $self_type, output_key: &TweakedPublicKey) -> $return_type { + pub fn fallback_v1_p2tr_tweaked($($self_mut)* $self: $self_type, output_key: &TweakedPublicKey) -> $return_type { let address = FallbackAddress { version: WitnessVersion::V1.to_num(), program: Vec::from(&output_key.serialize()[..]), @@ -375,7 +425,7 @@ macro_rules! invoice_builder_methods { ( /// Sets [`Bolt12Invoice::invoice_features`] to indicate MPP may be used. Otherwise, MPP is /// disallowed. - pub fn allow_mpp(mut $self: $self_type) -> $return_type { + pub fn allow_mpp($($self_mut)* $self: $self_type) -> $return_type { $self.invoice.fields_mut().features.set_basic_mpp_optional(); $return_value } @@ -390,7 +440,59 @@ impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> { } impl<'a, S: SigningPubkeyStrategy> InvoiceBuilder<'a, S> { - invoice_builder_methods!(self, Self, Self, self); + invoice_builder_methods!(self, Self, Self, self, S, mut); +} + +#[cfg(all(c_bindings, not(test)))] +impl<'a> InvoiceWithExplicitSigningPubkeyBuilder<'a> { + invoice_explicit_signing_pubkey_builder_methods!(self, &mut Self); + invoice_builder_methods!(self, &mut Self, (), (), ExplicitSigningPubkey); +} + +#[cfg(all(c_bindings, test))] +impl<'a> InvoiceWithExplicitSigningPubkeyBuilder<'a> { + invoice_explicit_signing_pubkey_builder_methods!(self, &mut Self); + invoice_builder_methods!(self, &mut Self, &mut Self, self, ExplicitSigningPubkey); +} + +#[cfg(all(c_bindings, not(test)))] +impl<'a> InvoiceWithDerivedSigningPubkeyBuilder<'a> { + invoice_derived_signing_pubkey_builder_methods!(self, &mut Self); + invoice_builder_methods!(self, &mut Self, (), (), DerivedSigningPubkey); +} + +#[cfg(all(c_bindings, test))] +impl<'a> InvoiceWithDerivedSigningPubkeyBuilder<'a> { + invoice_derived_signing_pubkey_builder_methods!(self, &mut Self); + invoice_builder_methods!(self, &mut Self, &mut Self, self, DerivedSigningPubkey); +} + +#[cfg(c_bindings)] +impl<'a> From> +for InvoiceBuilder<'a, ExplicitSigningPubkey> { + fn from(builder: InvoiceWithExplicitSigningPubkeyBuilder<'a>) -> Self { + let InvoiceWithExplicitSigningPubkeyBuilder { + invreq_bytes, invoice, signing_pubkey_strategy, + } = builder; + + Self { + invreq_bytes, invoice, signing_pubkey_strategy, + } + } +} + +#[cfg(c_bindings)] +impl<'a> From> +for InvoiceBuilder<'a, DerivedSigningPubkey> { + fn from(builder: InvoiceWithDerivedSigningPubkeyBuilder<'a>) -> Self { + let InvoiceWithDerivedSigningPubkeyBuilder { + invreq_bytes, invoice, signing_pubkey_strategy, + } = builder; + + Self { + invreq_bytes, invoice, signing_pubkey_strategy, + } + } } /// A semantically valid [`Bolt12Invoice`] that hasn't been signed. @@ -428,13 +530,13 @@ impl UnsignedBolt12Invoice { } } -macro_rules! unsigned_invoice_sign_method { ($self: ident, $self_type: ty) => { +macro_rules! unsigned_invoice_sign_method { ($self: ident, $self_type: ty $(, $self_mut: tt)?) => { /// Signs the [`TaggedHash`] of the invoice using the given function. /// /// Note: The hash computation may have included unknown, odd TLV records. /// /// This is not exported to bindings users as functions aren't currently mapped. - pub fn sign(mut $self: $self_type, sign: F) -> Result> + pub fn sign($($self_mut)* $self: $self_type, sign: F) -> Result> where F: FnOnce(&Self) -> Result { @@ -448,16 +550,31 @@ macro_rules! unsigned_invoice_sign_method { ($self: ident, $self_type: ty) => { signature_tlv_stream.write(&mut $self.bytes).unwrap(); Ok(Bolt12Invoice { + #[cfg(not(c_bindings))] bytes: $self.bytes, + #[cfg(c_bindings)] + bytes: $self.bytes.clone(), + #[cfg(not(c_bindings))] contents: $self.contents, + #[cfg(c_bindings)] + contents: $self.contents.clone(), signature, + #[cfg(not(c_bindings))] tagged_hash: $self.tagged_hash, + #[cfg(c_bindings)] + tagged_hash: $self.tagged_hash.clone(), }) } } } +#[cfg(not(c_bindings))] impl UnsignedBolt12Invoice { - unsigned_invoice_sign_method!(self, Self); + unsigned_invoice_sign_method!(self, Self, mut); +} + +#[cfg(c_bindings)] +impl UnsignedBolt12Invoice { + unsigned_invoice_sign_method!(self, &mut Self); } impl AsRef for UnsignedBolt12Invoice { @@ -1409,6 +1526,8 @@ mod tests { }, } + #[cfg(c_bindings)] + let mut unsigned_invoice = unsigned_invoice; let invoice = unsigned_invoice.sign(recipient_sign).unwrap(); let mut buffer = Vec::new(); @@ -2120,11 +2239,18 @@ mod tests { .request_invoice(vec![1; 32], payer_pubkey()).unwrap() .build().unwrap() .sign(payer_sign).unwrap(); + #[cfg(not(c_bindings))] + let invoice_builder = invoice_request + .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap(); + #[cfg(c_bindings)] let mut invoice_builder = invoice_request - .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() + .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap(); + let invoice_builder = invoice_builder .fallback_v0_p2wsh(&script.wscript_hash()) .fallback_v0_p2wpkh(&pubkey.wpubkey_hash().unwrap()) .fallback_v1_p2tr_tweaked(&tweaked_pubkey); + #[cfg(not(c_bindings))] + let mut invoice_builder = invoice_builder; // Only standard addresses will be included. let fallbacks = invoice_builder.invoice.fields_mut().fallbacks.as_mut().unwrap(); diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index 967db3cdd88..5f6d846ed10 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -71,7 +71,7 @@ use crate::ln::channelmanager::PaymentId; use crate::ln::features::InvoiceRequestFeatures; use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce}; use crate::ln::msgs::DecodeError; -use crate::offers::invoice::{BlindedPayInfo, DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder}; +use crate::offers::invoice::BlindedPayInfo; use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, self}; use crate::offers::offer::{Offer, OfferContents, OfferTlvStream, OfferTlvStreamRef}; use crate::offers::parse::{Bolt12ParseError, ParsedMessage, Bolt12SemanticError}; @@ -80,6 +80,15 @@ use crate::offers::signer::{Metadata, MetadataMaterial}; use crate::util::ser::{HighZeroBytesDroppedBigSize, SeekReadable, WithoutLength, Writeable, Writer}; use crate::util::string::PrintableString; +#[cfg(not(c_bindings))] +use { + crate::offers::invoice::{DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder}, +}; +#[cfg(c_bindings)] +use { + crate::offers::invoice::{InvoiceWithDerivedSigningPubkeyBuilder, InvoiceWithExplicitSigningPubkeyBuilder}, +}; + use crate::prelude::*; /// Tag for the hash function used when signing an [`InvoiceRequest`]'s merkle root. @@ -671,8 +680,6 @@ macro_rules! invoice_request_respond_with_explicit_signing_pubkey_methods { ( /// See [`InvoiceRequest::respond_with_no_std`] for further details where the aforementioned /// creation time is used for the `created_at` parameter. /// - /// This is not exported to bindings users as builder patterns don't map outside of move semantics. - /// /// [`Duration`]: core::time::Duration #[cfg(feature = "std")] pub fn respond_with( @@ -706,8 +713,6 @@ macro_rules! invoice_request_respond_with_explicit_signing_pubkey_methods { ( /// If the originating [`Offer`] was created using [`OfferBuilder::deriving_signing_pubkey`], /// then use [`InvoiceRequest::verify`] and [`VerifiedInvoiceRequest`] methods instead. /// - /// This is not exported to bindings users as builder patterns don't map outside of move semantics. - /// /// [`Bolt12Invoice::created_at`]: crate::offers::invoice::Bolt12Invoice::created_at /// [`OfferBuilder::deriving_signing_pubkey`]: crate::offers::offer::OfferBuilder::deriving_signing_pubkey pub fn respond_with_no_std( @@ -728,24 +733,45 @@ macro_rules! invoice_request_verify_method { ($self: ident, $self_type: ty) => { /// if they could be extracted from the metadata. /// /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice - pub fn verify( - $self: $self_type, key: &ExpandedKey, secp_ctx: &Secp256k1 + pub fn verify< + #[cfg(not(c_bindings))] + T: secp256k1::Signing + >( + $self: $self_type, key: &ExpandedKey, + #[cfg(not(c_bindings))] + secp_ctx: &Secp256k1, + #[cfg(c_bindings)] + secp_ctx: &Secp256k1, ) -> Result { let keys = $self.contents.inner.offer.verify(&$self.bytes, key, secp_ctx)?; Ok(VerifiedInvoiceRequest { + #[cfg(not(c_bindings))] inner: $self, + #[cfg(c_bindings)] + inner: $self.clone(), keys, }) } } } +#[cfg(not(c_bindings))] impl InvoiceRequest { offer_accessors!(self, self.contents.inner.offer); invoice_request_accessors!(self, self.contents); invoice_request_respond_with_explicit_signing_pubkey_methods!(self, self, InvoiceBuilder); invoice_request_verify_method!(self, Self); +} +#[cfg(c_bindings)] +impl InvoiceRequest { + offer_accessors!(self, self.contents.inner.offer); + invoice_request_accessors!(self, self.contents); + invoice_request_respond_with_explicit_signing_pubkey_methods!(self, self, InvoiceWithExplicitSigningPubkeyBuilder); + invoice_request_verify_method!(self, &Self); +} + +impl InvoiceRequest { /// Signature of the invoice request using [`payer_id`]. /// /// [`payer_id`]: Self::payer_id @@ -772,8 +798,6 @@ macro_rules! invoice_request_respond_with_derived_signing_pubkey_methods { ( /// /// See [`InvoiceRequest::respond_with`] for further details. /// - /// This is not exported to bindings users as builder patterns don't map outside of move semantics. - /// /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice #[cfg(feature = "std")] pub fn respond_using_derived_keys( @@ -792,8 +816,6 @@ macro_rules! invoice_request_respond_with_derived_signing_pubkey_methods { ( /// /// See [`InvoiceRequest::respond_with_no_std`] for further details. /// - /// This is not exported to bindings users as builder patterns don't map outside of move semantics. - /// /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice pub fn respond_using_derived_keys_no_std( &$self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash, @@ -817,8 +839,14 @@ macro_rules! invoice_request_respond_with_derived_signing_pubkey_methods { ( impl VerifiedInvoiceRequest { offer_accessors!(self, self.inner.contents.inner.offer); invoice_request_accessors!(self, self.inner.contents); + #[cfg(not(c_bindings))] invoice_request_respond_with_explicit_signing_pubkey_methods!(self, self.inner, InvoiceBuilder); + #[cfg(c_bindings)] + invoice_request_respond_with_explicit_signing_pubkey_methods!(self, self.inner, InvoiceWithExplicitSigningPubkeyBuilder); + #[cfg(not(c_bindings))] invoice_request_respond_with_derived_signing_pubkey_methods!(self, self.inner, InvoiceBuilder); + #[cfg(c_bindings)] + invoice_request_respond_with_derived_signing_pubkey_methods!(self, self.inner, InvoiceWithDerivedSigningPubkeyBuilder); } impl InvoiceRequestContents { diff --git a/lightning/src/offers/refund.rs b/lightning/src/offers/refund.rs index e06f045d309..1ae7637b091 100644 --- a/lightning/src/offers/refund.rs +++ b/lightning/src/offers/refund.rs @@ -96,7 +96,7 @@ use crate::ln::channelmanager::PaymentId; use crate::ln::features::InvoiceRequestFeatures; use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce}; use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT}; -use crate::offers::invoice::{BlindedPayInfo, DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder}; +use crate::offers::invoice::BlindedPayInfo; use crate::offers::invoice_request::{InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef}; use crate::offers::offer::{OfferTlvStream, OfferTlvStreamRef}; use crate::offers::parse::{Bech32Encode, Bolt12ParseError, Bolt12SemanticError, ParsedMessage}; @@ -105,6 +105,15 @@ use crate::offers::signer::{Metadata, MetadataMaterial, self}; use crate::util::ser::{SeekReadable, WithoutLength, Writeable, Writer}; use crate::util::string::PrintableString; +#[cfg(not(c_bindings))] +use { + crate::offers::invoice::{DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder}, +}; +#[cfg(c_bindings)] +use { + crate::offers::invoice::{InvoiceWithDerivedSigningPubkeyBuilder, InvoiceWithExplicitSigningPubkeyBuilder}, +}; + use crate::prelude::*; #[cfg(feature = "std")] @@ -589,11 +598,18 @@ macro_rules! respond_with_derived_signing_pubkey_methods { ($self: ident, $build } } } +#[cfg(not(c_bindings))] impl Refund { respond_with_explicit_signing_pubkey_methods!(self, InvoiceBuilder); respond_with_derived_signing_pubkey_methods!(self, InvoiceBuilder); } +#[cfg(c_bindings)] +impl Refund { + respond_with_explicit_signing_pubkey_methods!(self, InvoiceWithExplicitSigningPubkeyBuilder); + respond_with_derived_signing_pubkey_methods!(self, InvoiceWithDerivedSigningPubkeyBuilder); +} + #[cfg(test)] impl Refund { fn as_tlv_stream(&self) -> RefundTlvStreamRef { From 407762446cd4abaab5ea9c1c96e8c181a0f63b86 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Fri, 1 Mar 2024 10:17:08 -0600 Subject: [PATCH 11/13] Support BOLT 12 signing in c_bindings Replace the Fn trait bound on signing methods with a dedicated trait since Fn is not supported in bindings. Implement the trait for Fn so that closures can still be used in Rust. --- fuzz/src/invoice_request_deser.rs | 12 ++--- fuzz/src/offer_deser.rs | 6 +-- fuzz/src/refund_deser.rs | 6 +-- lightning/src/ln/channelmanager.rs | 6 ++- lightning/src/offers/invoice.rs | 63 ++++++++++++++++++------- lightning/src/offers/invoice_request.rs | 63 ++++++++++++++++++------- lightning/src/offers/merkle.rs | 50 ++++++++++++++------ lightning/src/offers/test_utils.rs | 4 ++ 8 files changed, 147 insertions(+), 63 deletions(-) diff --git a/fuzz/src/invoice_request_deser.rs b/fuzz/src/invoice_request_deser.rs index 22a2258f4e2..27178deacf0 100644 --- a/fuzz/src/invoice_request_deser.rs +++ b/fuzz/src/invoice_request_deser.rs @@ -37,17 +37,17 @@ pub fn do_test(data: &[u8], _out: Out) { let even_pubkey = x_only_pubkey.public_key(Parity::Even); if signing_pubkey == odd_pubkey || signing_pubkey == even_pubkey { unsigned_invoice - .sign::<_, Infallible>( - |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) - ) + .sign(|message: &UnsignedBolt12Invoice| -> Result<_, Infallible> { + Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) + }) .unwrap() .write(&mut buffer) .unwrap(); } else { unsigned_invoice - .sign::<_, Infallible>( - |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) - ) + .sign(|message: &UnsignedBolt12Invoice| -> Result<_, Infallible> { + Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) + }) .unwrap_err(); } } diff --git a/fuzz/src/offer_deser.rs b/fuzz/src/offer_deser.rs index e16c3b4103b..1b3289b3322 100644 --- a/fuzz/src/offer_deser.rs +++ b/fuzz/src/offer_deser.rs @@ -29,9 +29,9 @@ pub fn do_test(data: &[u8], _out: Out) { if let Ok(invoice_request) = build_response(&offer, pubkey) { invoice_request - .sign::<_, Infallible>( - |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) - ) + .sign(|message: &UnsignedInvoiceRequest| -> Result<_, Infallible> { + Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) + }) .unwrap() .write(&mut buffer) .unwrap(); diff --git a/fuzz/src/refund_deser.rs b/fuzz/src/refund_deser.rs index fd273d7e028..9c231aa5234 100644 --- a/fuzz/src/refund_deser.rs +++ b/fuzz/src/refund_deser.rs @@ -33,9 +33,9 @@ pub fn do_test(data: &[u8], _out: Out) { if let Ok(invoice) = build_response(&refund, pubkey, &secp_ctx) { invoice - .sign::<_, Infallible>( - |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) - ) + .sign(|message: &UnsignedBolt12Invoice| -> Result<_, Infallible> { + Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) + }) .unwrap() .write(&mut buffer) .unwrap(); diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index a56793fa16d..364150645e5 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -57,7 +57,7 @@ use crate::ln::msgs::{ChannelMessageHandler, DecodeError, LightningError}; use crate::ln::outbound_payment; use crate::ln::outbound_payment::{Bolt12PaymentError, OutboundPayments, PaymentAttempts, PendingOutboundPayment, SendAlongPathArgs, StaleExpiration}; use crate::ln::wire::Encode; -use crate::offers::invoice::{BlindedPayInfo, Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder}; +use crate::offers::invoice::{BlindedPayInfo, Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder, UnsignedBolt12Invoice}; use crate::offers::invoice_error::InvoiceError; use crate::offers::invoice_request::{DerivedPayerId, InvoiceRequestBuilder}; use crate::offers::merkle::SignError; @@ -9302,7 +9302,9 @@ where .and_then(|invoice| { #[cfg(c_bindings)] let mut invoice = invoice; - match invoice.sign(|invoice| self.node_signer.sign_bolt12_invoice(invoice)) { + match invoice.sign(|invoice: &UnsignedBolt12Invoice| + self.node_signer.sign_bolt12_invoice(invoice) + ) { Ok(invoice) => Ok(OffersMessage::Invoice(invoice)), Err(SignError::Signing(())) => Err(OffersMessage::InvoiceError( InvoiceError::from_string("Failed signing invoice".to_string()) diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index f665bd3b26e..50406738f36 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -23,6 +23,7 @@ //! use bitcoin::hashes::Hash; //! use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey}; //! use core::convert::{Infallible, TryFrom}; +//! use lightning::offers::invoice::UnsignedBolt12Invoice; //! use lightning::offers::invoice_request::InvoiceRequest; //! use lightning::offers::refund::Refund; //! use lightning::util::ser::Writeable; @@ -57,9 +58,9 @@ //! .allow_mpp() //! .fallback_v0_p2wpkh(&wpubkey_hash) //! .build()? -//! .sign::<_, Infallible>( -//! |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) -//! ) +//! .sign(|message: &UnsignedBolt12Invoice| -> Result<_, Infallible> { +//! Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) +//! }) //! .expect("failed verifying signature") //! .write(&mut buffer) //! .unwrap(); @@ -90,9 +91,9 @@ //! .allow_mpp() //! .fallback_v0_p2wpkh(&wpubkey_hash) //! .build()? -//! .sign::<_, Infallible>( -//! |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) -//! ) +//! .sign(|message: &UnsignedBolt12Invoice| -> Result<_, Infallible> { +//! Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) +//! }) //! .expect("failed verifying signature") //! .write(&mut buffer) //! .unwrap(); @@ -119,7 +120,7 @@ use crate::ln::features::{BlindedHopFeatures, Bolt12InvoiceFeatures, InvoiceRequ use crate::ln::inbound_payment::ExpandedKey; use crate::ln::msgs::DecodeError; use crate::offers::invoice_request::{INVOICE_REQUEST_PAYER_ID_TYPE, INVOICE_REQUEST_TYPES, IV_BYTES as INVOICE_REQUEST_IV_BYTES, InvoiceRequest, InvoiceRequestContents, InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef}; -use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, TlvStream, WithoutSignatures, self}; +use crate::offers::merkle::{SignError, SignFn, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, TlvStream, WithoutSignatures, self}; use crate::offers::offer::{Amount, OFFER_TYPES, OfferTlvStream, OfferTlvStreamRef, Quantity}; use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError, ParsedMessage}; use crate::offers::payer::{PAYER_METADATA_TYPE, PayerTlvStream, PayerTlvStreamRef}; @@ -324,9 +325,9 @@ macro_rules! invoice_derived_signing_pubkey_builder_methods { ($self: ident, $se let mut unsigned_invoice = UnsignedBolt12Invoice::new(invreq_bytes, invoice.clone()); let invoice = unsigned_invoice - .sign::<_, Infallible>( - |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) - ) + .sign(|message: &UnsignedBolt12Invoice| -> Result<_, Infallible> { + Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) + }) .unwrap(); Ok(invoice) } @@ -507,6 +508,37 @@ pub struct UnsignedBolt12Invoice { tagged_hash: TaggedHash, } +/// A function for signing an [`UnsignedBolt12Invoice`]. +pub trait SignBolt12InvoiceFn { + /// Error type returned by the function. + type Error; + + /// Signs a [`TaggedHash`] computed over the merkle root of `message`'s TLV stream. + fn sign_invoice(&self, message: &UnsignedBolt12Invoice) -> Result; +} + +impl SignBolt12InvoiceFn for F +where + F: Fn(&UnsignedBolt12Invoice) -> Result, +{ + type Error = E; + + fn sign_invoice(&self, message: &UnsignedBolt12Invoice) -> Result { + self(message) + } +} + +impl SignFn for F +where + F: SignBolt12InvoiceFn, +{ + type Error = E; + + fn sign(&self, message: &UnsignedBolt12Invoice) -> Result { + self.sign_invoice(message) + } +} + impl UnsignedBolt12Invoice { fn new(invreq_bytes: &[u8], contents: InvoiceContents) -> Self { // Use the invoice_request bytes instead of the invoice_request TLV stream as the latter may @@ -534,12 +566,9 @@ macro_rules! unsigned_invoice_sign_method { ($self: ident, $self_type: ty $(, $s /// Signs the [`TaggedHash`] of the invoice using the given function. /// /// Note: The hash computation may have included unknown, odd TLV records. - /// - /// This is not exported to bindings users as functions aren't currently mapped. - pub fn sign($($self_mut)* $self: $self_type, sign: F) -> Result> - where - F: FnOnce(&Self) -> Result - { + pub fn sign( + $($self_mut)* $self: $self_type, sign: F + ) -> Result> { let pubkey = $self.contents.fields().signing_pubkey; let signature = merkle::sign_message(sign, &$self, pubkey)?; @@ -2013,7 +2042,7 @@ mod tests { .sign(payer_sign).unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .build().unwrap() - .sign(|_| Err(())) + .sign(fail_sign) { Ok(_) => panic!("expected error"), Err(e) => assert_eq!(e, SignError::Signing(())), diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index 5f6d846ed10..4eed9fb7ee6 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -27,6 +27,7 @@ //! use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey}; //! use core::convert::Infallible; //! use lightning::ln::features::OfferFeatures; +//! use lightning::offers::invoice_request::UnsignedInvoiceRequest; //! use lightning::offers::offer::Offer; //! use lightning::util::ser::Writeable; //! @@ -47,9 +48,9 @@ //! .quantity(5)? //! .payer_note("foo".to_string()) //! .build()? -//! .sign::<_, Infallible>( -//! |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) -//! ) +//! .sign(|message: &UnsignedInvoiceRequest| -> Result<_, Infallible> { +//! Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) +//! }) //! .expect("failed verifying signature") //! .write(&mut buffer) //! .unwrap(); @@ -72,7 +73,7 @@ use crate::ln::features::InvoiceRequestFeatures; use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce}; use crate::ln::msgs::DecodeError; use crate::offers::invoice::BlindedPayInfo; -use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, self}; +use crate::offers::merkle::{SignError, SignFn, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, self}; use crate::offers::offer::{Offer, OfferContents, OfferTlvStream, OfferTlvStreamRef}; use crate::offers::parse::{Bolt12ParseError, ParsedMessage, Bolt12SemanticError}; use crate::offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef}; @@ -227,9 +228,9 @@ macro_rules! invoice_request_derived_payer_id_builder_methods { ( let secp_ctx = secp_ctx.unwrap(); let keys = keys.unwrap(); let invoice_request = unsigned_invoice_request - .sign::<_, Infallible>( - |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) - ) + .sign(|message: &UnsignedInvoiceRequest| -> Result<_, Infallible> { + Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) + }) .unwrap(); Ok(invoice_request) } @@ -493,6 +494,37 @@ pub struct UnsignedInvoiceRequest { tagged_hash: TaggedHash, } +/// A function for signing an [`UnsignedInvoiceRequest`]. +pub trait SignInvoiceRequestFn { + /// Error type returned by the function. + type Error; + + /// Signs a [`TaggedHash`] computed over the merkle root of `message`'s TLV stream. + fn sign_invoice_request(&self, message: &UnsignedInvoiceRequest) -> Result; +} + +impl SignInvoiceRequestFn for F +where + F: Fn(&UnsignedInvoiceRequest) -> Result, +{ + type Error = E; + + fn sign_invoice_request(&self, message: &UnsignedInvoiceRequest) -> Result { + self(message) + } +} + +impl SignFn for F +where + F: SignInvoiceRequestFn, +{ + type Error = E; + + fn sign(&self, message: &UnsignedInvoiceRequest) -> Result { + self.sign_invoice_request(message) + } +} + impl UnsignedInvoiceRequest { fn new(offer: &Offer, contents: InvoiceRequestContents) -> Self { // Use the offer bytes instead of the offer TLV stream as the offer may have contained @@ -522,12 +554,9 @@ macro_rules! unsigned_invoice_request_sign_method { ( /// Signs the [`TaggedHash`] of the invoice request using the given function. /// /// Note: The hash computation may have included unknown, odd TLV records. - /// - /// This is not exported to bindings users as functions are not yet mapped. - pub fn sign($($self_mut)* $self: $self_type, sign: F) -> Result> - where - F: FnOnce(&Self) -> Result - { + pub fn sign( + $($self_mut)* $self: $self_type, sign: F + ) -> Result> { let pubkey = $self.contents.payer_id; let signature = merkle::sign_message(sign, &$self, pubkey)?; @@ -1712,7 +1741,7 @@ mod tests { .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() .build().unwrap() - .sign(|_| Err(())) + .sign(fail_sign) { Ok(_) => panic!("expected error"), Err(e) => assert_eq!(e, SignError::Signing(())), @@ -2126,9 +2155,9 @@ mod tests { .build().unwrap() .request_invoice(vec![1; 32], keys.public_key()).unwrap() .build().unwrap() - .sign::<_, Infallible>( - |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) - ) + .sign(|message: &UnsignedInvoiceRequest| -> Result<_, Infallible> { + Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) + }) .unwrap(); let mut encoded_invoice_request = Vec::new(); diff --git a/lightning/src/offers/merkle.rs b/lightning/src/offers/merkle.rs index ced3b08b7c2..5a9424500b3 100644 --- a/lightning/src/offers/merkle.rs +++ b/lightning/src/offers/merkle.rs @@ -76,13 +76,33 @@ impl AsRef for TaggedHash { /// Error when signing messages. #[derive(Debug, PartialEq)] -pub enum SignError { +pub enum SignError { /// User-defined error when signing the message. Signing(E), /// Error when verifying the produced signature using the given pubkey. Verification(secp256k1::Error), } +/// A function for signing a [`TaggedHash`]. +pub(super) trait SignFn> { + /// Error type returned by the function. + type Error; + + /// Signs a [`TaggedHash`] computed over the merkle root of `message`'s TLV stream. + fn sign(&self, message: &T) -> Result; +} + +impl SignFn for F +where + F: Fn(&TaggedHash) -> Result, +{ + type Error = E; + + fn sign(&self, message: &TaggedHash) -> Result { + self(message) + } +} + /// Signs a [`TaggedHash`] computed over the merkle root of `message`'s TLV stream, checking if it /// can be verified with the supplied `pubkey`. /// @@ -92,14 +112,14 @@ pub enum SignError { /// /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest -pub(super) fn sign_message( - sign: F, message: &T, pubkey: PublicKey, +pub(super) fn sign_message( + f: F, message: &T, pubkey: PublicKey, ) -> Result> where - F: FnOnce(&T) -> Result, + F: SignFn, T: AsRef, { - let signature = sign(message).map_err(|e| SignError::Signing(e))?; + let signature = f.sign(message).map_err(|e| SignError::Signing(e))?; let digest = message.as_ref().as_digest(); let pubkey = pubkey.into(); @@ -278,7 +298,7 @@ mod tests { use bitcoin::secp256k1::schnorr::Signature; use core::convert::Infallible; use crate::offers::offer::{Amount, OfferBuilder}; - use crate::offers::invoice_request::InvoiceRequest; + use crate::offers::invoice_request::{InvoiceRequest, UnsignedInvoiceRequest}; use crate::offers::parse::Bech32Encode; use crate::offers::test_utils::{payer_pubkey, recipient_pubkey}; use crate::util::ser::Writeable; @@ -321,9 +341,9 @@ mod tests { .build_unchecked() .request_invoice(vec![0; 8], payer_keys.public_key()).unwrap() .build_unchecked() - .sign::<_, Infallible>( - |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &payer_keys)) - ) + .sign(|message: &UnsignedInvoiceRequest| -> Result<_, Infallible> { + Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &payer_keys)) + }) .unwrap(); assert_eq!( invoice_request.to_string(), @@ -375,9 +395,9 @@ mod tests { .build_unchecked() .request_invoice(vec![0; 8], payer_keys.public_key()).unwrap() .build_unchecked() - .sign::<_, Infallible>( - |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &payer_keys)) - ) + .sign(|message: &UnsignedInvoiceRequest| -> Result<_, Infallible> { + Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &payer_keys)) + }) .unwrap(); let mut bytes_without_signature = Vec::new(); @@ -407,9 +427,9 @@ mod tests { .build_unchecked() .request_invoice(vec![0; 8], payer_keys.public_key()).unwrap() .build_unchecked() - .sign::<_, Infallible>( - |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &payer_keys)) - ) + .sign(|message: &UnsignedInvoiceRequest| -> Result<_, Infallible> { + Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &payer_keys)) + }) .unwrap(); let tlv_stream = TlvStream::new(&invoice_request.bytes).range(0..1) diff --git a/lightning/src/offers/test_utils.rs b/lightning/src/offers/test_utils.rs index 39122472eac..701eb69c3e1 100644 --- a/lightning/src/offers/test_utils.rs +++ b/lightning/src/offers/test_utils.rs @@ -20,6 +20,10 @@ use crate::ln::features::BlindedHopFeatures; use crate::offers::invoice::BlindedPayInfo; use crate::offers::merkle::TaggedHash; +pub(crate) fn fail_sign>(_message: &T) -> Result { + Err(()) +} + pub(crate) fn payer_keys() -> KeyPair { let secp_ctx = Secp256k1::new(); KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap()) From 6b7050e8dc386519558a56acae3835695f9307ec Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Fri, 1 Mar 2024 10:27:54 -0600 Subject: [PATCH 12/13] Fix unused import warning --- lightning/src/crypto/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/lightning/src/crypto/mod.rs b/lightning/src/crypto/mod.rs index 6efd120667c..b4f5af8f119 100644 --- a/lightning/src/crypto/mod.rs +++ b/lightning/src/crypto/mod.rs @@ -1,3 +1,4 @@ +#[cfg(not(fuzzing))] use bitcoin::hashes::cmp::fixed_time_eq; pub(crate) mod chacha20; From 9277166a8b10bc0eff18fadc8fcff8a4a6a5673e Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Thu, 7 Mar 2024 16:59:09 -0600 Subject: [PATCH 13/13] Drop error type parameter from SignError SignError allows implementors of SignFunction to return a custom error type. Drop this as an unconstrained type causes problems with bindings and isn't useful unless the caller can take some sort of action based on different errors. --- fuzz/src/invoice_request_deser.rs | 10 +++--- fuzz/src/offer_deser.rs | 6 ++-- fuzz/src/refund_deser.rs | 6 ++-- lightning/src/ln/channelmanager.rs | 2 +- lightning/src/offers/invoice.rs | 41 ++++++++++-------------- lightning/src/offers/invoice_request.rs | 42 ++++++++++--------------- lightning/src/offers/merkle.rs | 38 ++++++++++------------ lightning/src/offers/test_utils.rs | 6 ++-- 8 files changed, 65 insertions(+), 86 deletions(-) diff --git a/fuzz/src/invoice_request_deser.rs b/fuzz/src/invoice_request_deser.rs index 27178deacf0..414ce1cdd1c 100644 --- a/fuzz/src/invoice_request_deser.rs +++ b/fuzz/src/invoice_request_deser.rs @@ -9,7 +9,7 @@ use bitcoin::secp256k1::{KeyPair, Parity, PublicKey, Secp256k1, SecretKey, self}; use crate::utils::test_logger; -use core::convert::{Infallible, TryFrom}; +use core::convert::TryFrom; use lightning::blinded_path::BlindedPath; use lightning::sign::EntropySource; use lightning::ln::PaymentHash; @@ -37,17 +37,17 @@ pub fn do_test(data: &[u8], _out: Out) { let even_pubkey = x_only_pubkey.public_key(Parity::Even); if signing_pubkey == odd_pubkey || signing_pubkey == even_pubkey { unsigned_invoice - .sign(|message: &UnsignedBolt12Invoice| -> Result<_, Infallible> { + .sign(|message: &UnsignedBolt12Invoice| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) - }) + ) .unwrap() .write(&mut buffer) .unwrap(); } else { unsigned_invoice - .sign(|message: &UnsignedBolt12Invoice| -> Result<_, Infallible> { + .sign(|message: &UnsignedBolt12Invoice| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) - }) + ) .unwrap_err(); } } diff --git a/fuzz/src/offer_deser.rs b/fuzz/src/offer_deser.rs index 1b3289b3322..5aad4642e46 100644 --- a/fuzz/src/offer_deser.rs +++ b/fuzz/src/offer_deser.rs @@ -9,7 +9,7 @@ use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey}; use crate::utils::test_logger; -use core::convert::{Infallible, TryFrom}; +use core::convert::TryFrom; use lightning::offers::invoice_request::UnsignedInvoiceRequest; use lightning::offers::offer::{Amount, Offer, Quantity}; use lightning::offers::parse::Bolt12SemanticError; @@ -29,9 +29,9 @@ pub fn do_test(data: &[u8], _out: Out) { if let Ok(invoice_request) = build_response(&offer, pubkey) { invoice_request - .sign(|message: &UnsignedInvoiceRequest| -> Result<_, Infallible> { + .sign(|message: &UnsignedInvoiceRequest| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) - }) + ) .unwrap() .write(&mut buffer) .unwrap(); diff --git a/fuzz/src/refund_deser.rs b/fuzz/src/refund_deser.rs index 9c231aa5234..14b136ec35b 100644 --- a/fuzz/src/refund_deser.rs +++ b/fuzz/src/refund_deser.rs @@ -9,7 +9,7 @@ use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey, self}; use crate::utils::test_logger; -use core::convert::{Infallible, TryFrom}; +use core::convert::TryFrom; use lightning::blinded_path::BlindedPath; use lightning::sign::EntropySource; use lightning::ln::PaymentHash; @@ -33,9 +33,9 @@ pub fn do_test(data: &[u8], _out: Out) { if let Ok(invoice) = build_response(&refund, pubkey, &secp_ctx) { invoice - .sign(|message: &UnsignedBolt12Invoice| -> Result<_, Infallible> { + .sign(|message: &UnsignedBolt12Invoice| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) - }) + ) .unwrap() .write(&mut buffer) .unwrap(); diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 364150645e5..00e281adf43 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -9306,7 +9306,7 @@ where self.node_signer.sign_bolt12_invoice(invoice) ) { Ok(invoice) => Ok(OffersMessage::Invoice(invoice)), - Err(SignError::Signing(())) => Err(OffersMessage::InvoiceError( + Err(SignError::Signing) => Err(OffersMessage::InvoiceError( InvoiceError::from_string("Failed signing invoice".to_string()) )), Err(SignError::Verification(_)) => Err(OffersMessage::InvoiceError( diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index 50406738f36..fc837102348 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -22,7 +22,7 @@ //! //! use bitcoin::hashes::Hash; //! use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey}; -//! use core::convert::{Infallible, TryFrom}; +//! use core::convert::TryFrom; //! use lightning::offers::invoice::UnsignedBolt12Invoice; //! use lightning::offers::invoice_request::InvoiceRequest; //! use lightning::offers::refund::Refund; @@ -58,9 +58,9 @@ //! .allow_mpp() //! .fallback_v0_p2wpkh(&wpubkey_hash) //! .build()? -//! .sign(|message: &UnsignedBolt12Invoice| -> Result<_, Infallible> { +//! .sign(|message: &UnsignedBolt12Invoice| //! Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) -//! }) +//! ) //! .expect("failed verifying signature") //! .write(&mut buffer) //! .unwrap(); @@ -91,9 +91,9 @@ //! .allow_mpp() //! .fallback_v0_p2wpkh(&wpubkey_hash) //! .build()? -//! .sign(|message: &UnsignedBolt12Invoice| -> Result<_, Infallible> { +//! .sign(|message: &UnsignedBolt12Invoice| //! Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) -//! }) +//! ) //! .expect("failed verifying signature") //! .write(&mut buffer) //! .unwrap(); @@ -110,7 +110,7 @@ use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, self}; use bitcoin::secp256k1::schnorr::Signature; use bitcoin::address::{Address, Payload, WitnessProgram, WitnessVersion}; use bitcoin::key::TweakedPublicKey; -use core::convert::{AsRef, Infallible, TryFrom}; +use core::convert::{AsRef, TryFrom}; use core::time::Duration; use crate::io; use crate::blinded_path::BlindedPath; @@ -325,9 +325,9 @@ macro_rules! invoice_derived_signing_pubkey_builder_methods { ($self: ident, $se let mut unsigned_invoice = UnsignedBolt12Invoice::new(invreq_bytes, invoice.clone()); let invoice = unsigned_invoice - .sign(|message: &UnsignedBolt12Invoice| -> Result<_, Infallible> { + .sign(|message: &UnsignedBolt12Invoice| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) - }) + ) .unwrap(); Ok(invoice) } @@ -510,31 +510,24 @@ pub struct UnsignedBolt12Invoice { /// A function for signing an [`UnsignedBolt12Invoice`]. pub trait SignBolt12InvoiceFn { - /// Error type returned by the function. - type Error; - /// Signs a [`TaggedHash`] computed over the merkle root of `message`'s TLV stream. - fn sign_invoice(&self, message: &UnsignedBolt12Invoice) -> Result; + fn sign_invoice(&self, message: &UnsignedBolt12Invoice) -> Result; } -impl SignBolt12InvoiceFn for F +impl SignBolt12InvoiceFn for F where - F: Fn(&UnsignedBolt12Invoice) -> Result, + F: Fn(&UnsignedBolt12Invoice) -> Result, { - type Error = E; - - fn sign_invoice(&self, message: &UnsignedBolt12Invoice) -> Result { + fn sign_invoice(&self, message: &UnsignedBolt12Invoice) -> Result { self(message) } } -impl SignFn for F +impl SignFn for F where - F: SignBolt12InvoiceFn, + F: SignBolt12InvoiceFn, { - type Error = E; - - fn sign(&self, message: &UnsignedBolt12Invoice) -> Result { + fn sign(&self, message: &UnsignedBolt12Invoice) -> Result { self.sign_invoice(message) } } @@ -568,7 +561,7 @@ macro_rules! unsigned_invoice_sign_method { ($self: ident, $self_type: ty $(, $s /// Note: The hash computation may have included unknown, odd TLV records. pub fn sign( $($self_mut)* $self: $self_type, sign: F - ) -> Result> { + ) -> Result { let pubkey = $self.contents.fields().signing_pubkey; let signature = merkle::sign_message(sign, &$self, pubkey)?; @@ -2045,7 +2038,7 @@ mod tests { .sign(fail_sign) { Ok(_) => panic!("expected error"), - Err(e) => assert_eq!(e, SignError::Signing(())), + Err(e) => assert_eq!(e, SignError::Signing), } match OfferBuilder::new("foo".into(), recipient_pubkey()) diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index 4eed9fb7ee6..f282075d933 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -25,7 +25,6 @@ //! //! use bitcoin::network::constants::Network; //! use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey}; -//! use core::convert::Infallible; //! use lightning::ln::features::OfferFeatures; //! use lightning::offers::invoice_request::UnsignedInvoiceRequest; //! use lightning::offers::offer::Offer; @@ -48,9 +47,9 @@ //! .quantity(5)? //! .payer_note("foo".to_string()) //! .build()? -//! .sign(|message: &UnsignedInvoiceRequest| -> Result<_, Infallible> { +//! .sign(|message: &UnsignedInvoiceRequest| //! Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) -//! }) +//! ) //! .expect("failed verifying signature") //! .write(&mut buffer) //! .unwrap(); @@ -62,7 +61,7 @@ use bitcoin::blockdata::constants::ChainHash; use bitcoin::network::constants::Network; use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, self}; use bitcoin::secp256k1::schnorr::Signature; -use core::convert::{AsRef, Infallible, TryFrom}; +use core::convert::{AsRef, TryFrom}; use core::ops::Deref; use crate::sign::EntropySource; use crate::io; @@ -228,9 +227,9 @@ macro_rules! invoice_request_derived_payer_id_builder_methods { ( let secp_ctx = secp_ctx.unwrap(); let keys = keys.unwrap(); let invoice_request = unsigned_invoice_request - .sign(|message: &UnsignedInvoiceRequest| -> Result<_, Infallible> { + .sign(|message: &UnsignedInvoiceRequest| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) - }) + ) .unwrap(); Ok(invoice_request) } @@ -496,31 +495,24 @@ pub struct UnsignedInvoiceRequest { /// A function for signing an [`UnsignedInvoiceRequest`]. pub trait SignInvoiceRequestFn { - /// Error type returned by the function. - type Error; - /// Signs a [`TaggedHash`] computed over the merkle root of `message`'s TLV stream. - fn sign_invoice_request(&self, message: &UnsignedInvoiceRequest) -> Result; + fn sign_invoice_request(&self, message: &UnsignedInvoiceRequest) -> Result; } -impl SignInvoiceRequestFn for F +impl SignInvoiceRequestFn for F where - F: Fn(&UnsignedInvoiceRequest) -> Result, + F: Fn(&UnsignedInvoiceRequest) -> Result, { - type Error = E; - - fn sign_invoice_request(&self, message: &UnsignedInvoiceRequest) -> Result { + fn sign_invoice_request(&self, message: &UnsignedInvoiceRequest) -> Result { self(message) } } -impl SignFn for F +impl SignFn for F where - F: SignInvoiceRequestFn, + F: SignInvoiceRequestFn, { - type Error = E; - - fn sign(&self, message: &UnsignedInvoiceRequest) -> Result { + fn sign(&self, message: &UnsignedInvoiceRequest) -> Result { self.sign_invoice_request(message) } } @@ -556,7 +548,7 @@ macro_rules! unsigned_invoice_request_sign_method { ( /// Note: The hash computation may have included unknown, odd TLV records. pub fn sign( $($self_mut)* $self: $self_type, sign: F - ) -> Result> { + ) -> Result { let pubkey = $self.contents.payer_id; let signature = merkle::sign_message(sign, &$self, pubkey)?; @@ -1111,7 +1103,7 @@ mod tests { use bitcoin::blockdata::constants::ChainHash; use bitcoin::network::constants::Network; use bitcoin::secp256k1::{KeyPair, Secp256k1, SecretKey, self}; - use core::convert::{Infallible, TryFrom}; + use core::convert::TryFrom; use core::num::NonZeroU64; #[cfg(feature = "std")] use core::time::Duration; @@ -1744,7 +1736,7 @@ mod tests { .sign(fail_sign) { Ok(_) => panic!("expected error"), - Err(e) => assert_eq!(e, SignError::Signing(())), + Err(e) => assert_eq!(e, SignError::Signing), } match OfferBuilder::new("foo".into(), recipient_pubkey()) @@ -2155,9 +2147,9 @@ mod tests { .build().unwrap() .request_invoice(vec![1; 32], keys.public_key()).unwrap() .build().unwrap() - .sign(|message: &UnsignedInvoiceRequest| -> Result<_, Infallible> { + .sign(|message: &UnsignedInvoiceRequest| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) - }) + ) .unwrap(); let mut encoded_invoice_request = Vec::new(); diff --git a/lightning/src/offers/merkle.rs b/lightning/src/offers/merkle.rs index 5a9424500b3..941bf196716 100644 --- a/lightning/src/offers/merkle.rs +++ b/lightning/src/offers/merkle.rs @@ -76,29 +76,24 @@ impl AsRef for TaggedHash { /// Error when signing messages. #[derive(Debug, PartialEq)] -pub enum SignError { +pub enum SignError { /// User-defined error when signing the message. - Signing(E), + Signing, /// Error when verifying the produced signature using the given pubkey. Verification(secp256k1::Error), } /// A function for signing a [`TaggedHash`]. pub(super) trait SignFn> { - /// Error type returned by the function. - type Error; - /// Signs a [`TaggedHash`] computed over the merkle root of `message`'s TLV stream. - fn sign(&self, message: &T) -> Result; + fn sign(&self, message: &T) -> Result; } -impl SignFn for F +impl SignFn for F where - F: Fn(&TaggedHash) -> Result, + F: Fn(&TaggedHash) -> Result, { - type Error = E; - - fn sign(&self, message: &TaggedHash) -> Result { + fn sign(&self, message: &TaggedHash) -> Result { self(message) } } @@ -112,14 +107,14 @@ where /// /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest -pub(super) fn sign_message( +pub(super) fn sign_message( f: F, message: &T, pubkey: PublicKey, -) -> Result> +) -> Result where - F: SignFn, + F: SignFn, T: AsRef, { - let signature = f.sign(message).map_err(|e| SignError::Signing(e))?; + let signature = f.sign(message).map_err(|()| SignError::Signing)?; let digest = message.as_ref().as_digest(); let pubkey = pubkey.into(); @@ -296,7 +291,6 @@ mod tests { use bitcoin::hashes::hex::FromHex; use bitcoin::secp256k1::{KeyPair, Message, Secp256k1, SecretKey}; use bitcoin::secp256k1::schnorr::Signature; - use core::convert::Infallible; use crate::offers::offer::{Amount, OfferBuilder}; use crate::offers::invoice_request::{InvoiceRequest, UnsignedInvoiceRequest}; use crate::offers::parse::Bech32Encode; @@ -341,9 +335,9 @@ mod tests { .build_unchecked() .request_invoice(vec![0; 8], payer_keys.public_key()).unwrap() .build_unchecked() - .sign(|message: &UnsignedInvoiceRequest| -> Result<_, Infallible> { + .sign(|message: &UnsignedInvoiceRequest| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &payer_keys)) - }) + ) .unwrap(); assert_eq!( invoice_request.to_string(), @@ -395,9 +389,9 @@ mod tests { .build_unchecked() .request_invoice(vec![0; 8], payer_keys.public_key()).unwrap() .build_unchecked() - .sign(|message: &UnsignedInvoiceRequest| -> Result<_, Infallible> { + .sign(|message: &UnsignedInvoiceRequest| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &payer_keys)) - }) + ) .unwrap(); let mut bytes_without_signature = Vec::new(); @@ -427,9 +421,9 @@ mod tests { .build_unchecked() .request_invoice(vec![0; 8], payer_keys.public_key()).unwrap() .build_unchecked() - .sign(|message: &UnsignedInvoiceRequest| -> Result<_, Infallible> { + .sign(|message: &UnsignedInvoiceRequest| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &payer_keys)) - }) + ) .unwrap(); let tlv_stream = TlvStream::new(&invoice_request.bytes).range(0..1) diff --git a/lightning/src/offers/test_utils.rs b/lightning/src/offers/test_utils.rs index 701eb69c3e1..29bed53d83f 100644 --- a/lightning/src/offers/test_utils.rs +++ b/lightning/src/offers/test_utils.rs @@ -11,7 +11,7 @@ use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey}; use bitcoin::secp256k1::schnorr::Signature; -use core::convert::{AsRef, Infallible}; +use core::convert::AsRef; use core::time::Duration; use crate::blinded_path::{BlindedHop, BlindedPath}; use crate::sign::EntropySource; @@ -29,7 +29,7 @@ pub(crate) fn payer_keys() -> KeyPair { KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap()) } -pub(crate) fn payer_sign>(message: &T) -> Result { +pub(crate) fn payer_sign>(message: &T) -> Result { let secp_ctx = Secp256k1::new(); let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap()); Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) @@ -44,7 +44,7 @@ pub(crate) fn recipient_keys() -> KeyPair { KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[43; 32]).unwrap()) } -pub(crate) fn recipient_sign>(message: &T) -> Result { +pub(crate) fn recipient_sign>(message: &T) -> Result { let secp_ctx = Secp256k1::new(); let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[43; 32]).unwrap()); Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))