diff --git a/fuzz/src/invoice_request_deser.rs b/fuzz/src/invoice_request_deser.rs index 22a2258f4e2..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,16 +37,16 @@ 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| + 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| + 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..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,8 +29,8 @@ 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| + Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) ) .unwrap() .write(&mut buffer) diff --git a/fuzz/src/refund_deser.rs b/fuzz/src/refund_deser.rs index fd273d7e028..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,8 +33,8 @@ 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| + Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) ) .unwrap() .write(&mut buffer) 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; diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 11413db612a..8dfd0c8fcaa 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -58,10 +58,11 @@ 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, UnsignedBolt12Invoice}; use crate::offers::invoice_error::InvoiceError; +use crate::offers::invoice_request::{DerivedPayerId, InvoiceRequestBuilder}; 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}; @@ -77,11 +78,17 @@ 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, + crate::offers::refund::RefundMaybeWithDerivedMetadataBuilder, +}; use alloc::collections::{btree_map, BTreeMap}; @@ -7633,7 +7640,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. @@ -7662,23 +7671,25 @@ 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()) } +} } +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. /// @@ -7728,31 +7739,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 @@ -7816,9 +7849,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)?, @@ -7912,6 +7947,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)?; @@ -9424,6 +9460,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())), @@ -9435,18 +9473,25 @@ 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| - match invoice.sign(|invoice| self.node_signer.sign_bolt12_invoice(invoice)) { + .and_then(|invoice| { + #[cfg(c_bindings)] + let mut 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( + Err(SignError::Signing) => Err(OffersMessage::InvoiceError( InvoiceError::from_string("Failed signing invoice".to_string()) )), 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 bb29c76164e..fc837102348 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -22,13 +22,14 @@ //! //! 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; //! 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 +45,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,12 +53,13 @@ #![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) //! .build()? -//! .sign::<_, Infallible>( -//! |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) +//! .sign(|message: &UnsignedBolt12Invoice| +//! Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) //! ) //! .expect("failed verifying signature") //! .write(&mut buffer) @@ -74,6 +77,7 @@ //! # let mut buffer = Vec::new(); //! //! // Invoice for the "offer for money" flow. +//! # >::from( //! "lnr1qcp4256ypq" //! .parse::()? #![cfg_attr(feature = "std", doc = " @@ -82,12 +86,13 @@ #![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) //! .build()? -//! .sign::<_, Infallible>( -//! |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) +//! .sign(|message: &UnsignedBolt12Invoice| +//! Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) //! ) //! .expect("failed verifying signature") //! .write(&mut buffer) @@ -105,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; @@ -115,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}; @@ -151,6 +156,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. @@ -169,7 +206,8 @@ 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) => { + #[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 +224,7 @@ impl<'a> InvoiceBuilder<'a, ExplicitSigningPubkey> { 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 @@ -200,9 +239,34 @@ impl<'a> InvoiceBuilder<'a, ExplicitSigningPubkey> { Self::new(&refund.bytes, contents, ExplicitSigningPubkey {}) } -} -impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> { + /// Builds an unsigned [`Bolt12Invoice`] after checking for valid semantics. It can be signed by + /// [`UnsignedBolt12Invoice::sign`]. + pub fn build($self: $self_type) -> 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 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 @@ -219,6 +283,7 @@ impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> { 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, @@ -234,9 +299,43 @@ impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> { Self::new(&refund.bytes, contents, DerivedSigningPubkey(keys)) } -} -impl<'a, S: SigningPubkeyStrategy> InvoiceBuilder<'a, S> { + /// Builds a signed [`Bolt12Invoice`] after checking for valid semantics. + pub fn build_and_sign( + $self: $self_type, 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 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(|message: &UnsignedBolt12Invoice| + Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) + ) + .unwrap(); + Ok(invoice) + } +} } + +macro_rules! invoice_builder_methods { ( + $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 ) -> Result { @@ -253,6 +352,7 @@ impl<'a, S: SigningPubkeyStrategy> InvoiceBuilder<'a, S> { } } + #[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 @@ -263,8 +363,9 @@ impl<'a, S: SigningPubkeyStrategy> InvoiceBuilder<'a, S> { } } + #[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); @@ -278,108 +379,120 @@ 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($($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); - 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($($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()), }; - 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($($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()), }; - 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($($self_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($($self_mut)* $self: $self_type) -> $return_type { + $self.invoice.fields_mut().features.set_basic_mpp_optional(); + $return_value } -} +} } 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); - } - } + invoice_explicit_signing_pubkey_builder_methods!(self, Self); +} - #[cfg(not(feature = "std"))] { - if self.invoice.is_offer_or_refund_expired_no_std(self.invoice.created_at()) { - return Err(Bolt12SemanticError::AlreadyExpired); - } - } +impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> { + invoice_derived_signing_pubkey_builder_methods!(self, Self); +} - let InvoiceBuilder { invreq_bytes, invoice, .. } = self; - Ok(UnsignedBolt12Invoice::new(invreq_bytes, invoice)) - } +impl<'a, S: SigningPubkeyStrategy> InvoiceBuilder<'a, S> { + invoice_builder_methods!(self, Self, Self, self, S, mut); } -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(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(not(feature = "std"))] { - if self.invoice.is_offer_or_refund_expired_no_std(self.invoice.created_at()) { - return Err(Bolt12SemanticError::AlreadyExpired); - } +#[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, } + } +} - let InvoiceBuilder { - invreq_bytes, invoice, signing_pubkey_strategy: DerivedSigningPubkey(keys) - } = self; - let unsigned_invoice = UnsignedBolt12Invoice::new(invreq_bytes, invoice); +#[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; - let invoice = unsigned_invoice - .sign::<_, Infallible>( - |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) - ) - .unwrap(); - Ok(invoice) + Self { + invreq_bytes, invoice, signing_pubkey_strategy, + } } } @@ -395,6 +508,30 @@ pub struct UnsignedBolt12Invoice { tagged_hash: TaggedHash, } +/// A function for signing an [`UnsignedBolt12Invoice`]. +pub trait SignBolt12InvoiceFn { + /// 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, +{ + fn sign_invoice(&self, message: &UnsignedBolt12Invoice) -> Result { + self(message) + } +} + +impl SignFn for F +where + F: SignBolt12InvoiceFn, +{ + 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 @@ -416,32 +553,50 @@ impl UnsignedBolt12Invoice { pub fn tagged_hash(&self) -> &TaggedHash { &self.tagged_hash } +} +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, sign: F) -> Result> - where - F: FnOnce(&Self) -> Result - { - let pubkey = self.contents.fields().signing_pubkey; - let signature = merkle::sign_message(sign, &self, pubkey)?; + 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)?; // 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, + #[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, - tagged_hash: self.tagged_hash, + #[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, mut); +} + +#[cfg(c_bindings)] +impl UnsignedBolt12Invoice { + unsigned_invoice_sign_method!(self, &mut Self); } impl AsRef for UnsignedBolt12Invoice { @@ -1306,10 +1461,19 @@ 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, + 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; @@ -1384,6 +1548,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(); @@ -1644,6 +1810,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) @@ -1867,10 +2035,10 @@ 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(())), + Err(e) => assert_eq!(e, SignError::Signing), } match OfferBuilder::new("foo".into(), recipient_pubkey()) @@ -2093,11 +2261,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 4dd85b352f7..f282075d933 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -25,8 +25,8 @@ //! //! 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; //! use lightning::util::ser::Writeable; //! @@ -36,16 +36,19 @@ //! 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)? //! .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| +//! Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) //! ) //! .expect("failed verifying signature") //! .write(&mut buffer) @@ -58,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; @@ -68,8 +71,8 @@ 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::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, self}; +use crate::offers::invoice::BlindedPayInfo; +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}; @@ -77,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. @@ -99,6 +111,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. @@ -117,7 +157,8 @@ 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) => { + #[cfg_attr(c_bindings, allow(dead_code))] pub(super) fn new(offer: &'a Offer, metadata: Vec, payer_id: PublicKey) -> Self { Self { offer, @@ -128,6 +169,7 @@ impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, ExplicitPayerI } } + #[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, @@ -144,12 +186,23 @@ impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, ExplicitPayerI secp_ctx: None, } } -} -impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, DerivedPayerId, T> { + /// Builds an unsigned [`InvoiceRequest`] after checking for valid semantics. It can be signed + /// by [`UnsignedInvoiceRequest::sign`]. + 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) + } +} } + +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); @@ -163,9 +216,29 @@ impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, DerivedPayerId secp_ctx: Some(secp_ctx), } } -} -impl<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, P, T> { + /// 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(); + let keys = keys.unwrap(); + let invoice_request = unsigned_invoice_request + .sign(|message: &UnsignedInvoiceRequest| + Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) + ) + .unwrap(); + Ok(invoice_request) + } +} } + +macro_rules! invoice_request_builder_methods { ( + $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 { @@ -179,8 +252,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 @@ -188,13 +261,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($($self_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 @@ -203,156 +276,208 @@ 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($($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 )?; - 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($($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) } /// 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($($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) -> 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")] { - 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) -> - (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; - 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, + #[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); + let unsigned_invoice_request = UnsignedInvoiceRequest::new($self.offer, invoice_request); (unsigned_invoice_request, keys, secp_ctx) } -} +} } -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) +#[cfg(test)] +macro_rules! invoice_request_builder_test_methods { ( + $self: ident, $self_type: ty, $return_type: ty, $return_value: expr $(, $self_mut: tt)? +) => { + #[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 } -} -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_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 } -} -#[cfg(test)] -impl<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, P, T> { - fn chain_unchecked(mut self, network: Network) -> Self { - let chain = ChainHash::using_genesis_block(network); - self.invoice_request.chain = Some(chain); - self + #[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 amount_msats_unchecked(mut self, amount_msats: u64) -> Self { - self.invoice_request.amount_msats = Some(amount_msats); - self + #[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 } - fn features_unchecked(mut self, features: InvoiceRequestFeatures) -> Self { - self.invoice_request.features = features; - self + #[cfg_attr(c_bindings, allow(dead_code))] + 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, T); +} + +impl<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, P, T> { + invoice_request_builder_methods!(self, Self, Self, self, T, mut); + + #[cfg(test)] + 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); +} - fn quantity_unchecked(mut self, quantity: u64) -> Self { - self.invoice_request.quantity = Some(quantity); - 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; - pub(super) fn build_unchecked(self) -> UnsignedInvoiceRequest { - self.build_without_checks().0 + Self { + offer, invoice_request, payer_id, payer_id_strategy, secp_ctx, + } } } @@ -368,6 +493,30 @@ pub struct UnsignedInvoiceRequest { tagged_hash: TaggedHash, } +/// A function for signing an [`UnsignedInvoiceRequest`]. +pub trait SignInvoiceRequestFn { + /// 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, +{ + fn sign_invoice_request(&self, message: &UnsignedInvoiceRequest) -> Result { + self(message) + } +} + +impl SignFn for F +where + F: SignInvoiceRequestFn, +{ + 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 @@ -389,31 +538,48 @@ impl UnsignedInvoiceRequest { pub fn tagged_hash(&self) -> &TaggedHash { &self.tagged_hash } +} +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, sign: F) -> Result> - where - F: FnOnce(&Self) -> Result - { - let pubkey = self.contents.payer_id; - let signature = merkle::sign_message(sign, &self, pubkey)?; + 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)?; // 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, + #[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, &mut Self); } impl AsRef for UnsignedInvoiceRequest { @@ -526,35 +692,25 @@ 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. /// /// 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( - &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. @@ -578,36 +734,72 @@ impl InvoiceRequest { /// 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( - &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 + 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)?; + let keys = $self.contents.inner.offer.verify(&$self.bytes, key, secp_ctx)?; Ok(VerifiedInvoiceRequest { - inner: self, + #[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 + 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(); @@ -618,55 +810,25 @@ 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. /// /// 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( - &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 @@ -675,26 +837,37 @@ impl VerifiedInvoiceRequest { /// /// 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, + &$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); + #[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 { @@ -930,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; @@ -941,7 +1114,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::*; @@ -955,6 +1136,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(); @@ -1550,10 +1733,10 @@ 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(())), + Err(e) => assert_eq!(e, SignError::Signing), } match OfferBuilder::new("foo".into(), recipient_pubkey()) @@ -1964,8 +2147,8 @@ 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| + Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) ) .unwrap(); diff --git a/lightning/src/offers/merkle.rs b/lightning/src/offers/merkle.rs index ced3b08b7c2..941bf196716 100644 --- a/lightning/src/offers/merkle.rs +++ b/lightning/src/offers/merkle.rs @@ -76,13 +76,28 @@ 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> { + /// 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, +{ + 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 +107,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, -) -> Result> +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(|()| SignError::Signing)?; let digest = message.as_ref().as_digest(); let pubkey = pubkey.into(); @@ -276,9 +291,8 @@ 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; + 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,8 +335,8 @@ 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| + Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &payer_keys)) ) .unwrap(); assert_eq!( @@ -375,8 +389,8 @@ 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| + Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &payer_keys)) ) .unwrap(); @@ -407,8 +421,8 @@ 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| + Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &payer_keys)) ) .unwrap(); diff --git a/lightning/src/offers/offer.rs b/lightning/src/offers/offer.rs index d71ec2afd04..e9e35836a96 100644 --- a/lightning/src/offers/offer.rs +++ b/lightning/src/offers/offer.rs @@ -92,13 +92,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")] @@ -119,6 +127,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. @@ -135,9 +171,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. @@ -152,7 +191,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, @@ -166,13 +205,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 { ($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 @@ -186,12 +225,12 @@ impl<'a, T: secp256k1::Signing> OfferBuilder<'a, DerivedMetadata, T> { /// [`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); 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, @@ -201,17 +240,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 $(, $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. /// /// 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 @@ -220,45 +261,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($($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); } - 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($($self_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($($self_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($($self_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 @@ -266,23 +307,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($($self_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($($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) -> Result { - match self.offer.amount { + pub fn build($($self_mut)* $self: $self_type) -> Result { + match $self.offer.amount { Some(Amount::Bitcoin { amount_msats }) => { if amount_msats > MAX_VALUE_MSAT { return Err(Bolt12SemanticError::InvalidAmount); @@ -292,61 +333,123 @@ 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($($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 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(); - - Offer { bytes, contents: self.offer } + $self.offer.write(&mut bytes).unwrap(); + + Offer { + bytes, + #[cfg(not(c_bindings))] + contents: $self.offer, + #[cfg(c_bindings)] + contents: $self.offer.clone() + } } -} +} } #[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 $(, $self_mut: tt)? +) => { + #[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 { - self.offer.paths = None; - self + #[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 } - pub(super) fn build_unchecked(self) -> Offer { - self.build_without_checks() + #[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, mut); + + #[cfg(test)] + offer_builder_test_methods!(self, Self, Self, self, mut); +} + +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!(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 } } } @@ -489,7 +592,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, @@ -501,50 +606,52 @@ impl Offer { /// /// 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, Bolt12SemanticError> + ) -> Result<$builder, Bolt12SemanticError> where ES::Target: EntropySource, { - if self.offer_features().requires_unknown_bits() { + if $self.offer_features().requires_unknown_bits() { return Err(Bolt12SemanticError::UnknownRequiredFeatures); } - Ok(InvoiceRequestBuilder::deriving_payer_id( - self, expanded_key, entropy_source, secp_ctx, payment_id - )) + Ok(<$builder>::deriving_payer_id($self, expanded_key, entropy_source, secp_ctx, payment_id)) } +} } +macro_rules! request_invoice_explicit_payer_id { ($self: ident, $builder: ty) => { /// Similar to [`Offer::request_invoice_deriving_payer_id`] except uses `payer_id` for the /// [`InvoiceRequest::payer_id`] instead of deriving a different key for each request. /// /// Useful for recurring payments using the same `payer_id` with different invoices. /// - /// This is not exported to bindings users as builder patterns don't map outside of move semantics. - /// /// [`InvoiceRequest::payer_id`]: crate::offers::invoice_request::InvoiceRequest::payer_id pub fn request_invoice_deriving_metadata( - &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`, @@ -559,20 +666,32 @@ impl Offer { /// /// 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 - ) -> 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)] +#[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 { self.contents.as_tlv_stream() } @@ -928,7 +1047,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; @@ -1057,6 +1184,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) @@ -1113,6 +1242,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) @@ -1167,8 +1298,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)); diff --git a/lightning/src/offers/refund.rs b/lightning/src/offers/refund.rs index 5107e31d8d0..42177960868 100644 --- a/lightning/src/offers/refund.rs +++ b/lightning/src/offers/refund.rs @@ -97,7 +97,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}; @@ -106,6 +106,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")] @@ -125,7 +134,18 @@ pub struct RefundBuilder<'a, T: secp256k1::Signing> { secp_ctx: Option<&'a Secp256k1>, } -impl<'a> RefundBuilder<'a, secp256k1::SignOnly> { +/// 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. /// @@ -156,9 +176,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, $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 /// different payer id for each refund, assuming a different nonce is used. Otherwise, the @@ -174,7 +196,7 @@ impl<'a, T: secp256k1::Signing> RefundBuilder<'a, T> { /// [`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); @@ -198,17 +220,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($($self_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($($self_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 @@ -216,26 +238,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($($self_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($($self_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 @@ -247,65 +269,109 @@ 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($($self_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($($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) -> Result { - if self.refund.chain() == self.refund.implied_chain() { - self.refund.chain = None; + pub fn build($($self_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, + #[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_mut: tt)? +) => { + #[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 + } - Ok(Refund { bytes, contents: self.refund }) + #[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_explicit_metadata_builder_methods!(); } -#[cfg(test)] impl<'a, T: secp256k1::Signing> RefundBuilder<'a, T> { - pub(crate) fn clear_paths(mut self) -> Self { - self.refund.paths = None; - self - } + refund_builder_methods!(self, Self, Self, self, T, mut); + + #[cfg(test)] + 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); +} - fn features_unchecked(mut self, features: InvoiceRequestFeatures) -> Self { - self.refund.features = features; - 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 } } } @@ -423,7 +489,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. /// @@ -435,14 +503,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. @@ -468,16 +536,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`]. /// @@ -488,9 +558,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, { @@ -498,7 +568,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 ) } @@ -512,22 +582,36 @@ 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)] +#[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 { self.contents.as_tlv_stream() } @@ -797,7 +881,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; diff --git a/lightning/src/offers/test_utils.rs b/lightning/src/offers/test_utils.rs index 39122472eac..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; @@ -20,12 +20,16 @@ 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()) } -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)) @@ -40,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))