From 07d628e0faf1a953a660144e3da77b0542027f84 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Sun, 25 Feb 2024 14:58:07 -0600 Subject: [PATCH] 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 9e5922692..457f4ac30 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 792214d52..967db3cdd 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 f4e9c4a8c..e06f045d3 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() } -- 2.39.5