X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Foffers%2Finvoice_request.rs;h=9157613fcd977a132e9f7b5751f196af0592269a;hb=478911d42a8b47020511bdcb793756723f3801be;hp=41c86171b1bf31be953cdb1d9288ced33fe03413;hpb=63d0d5583d2b18021434092face066ae92d5ae7b;p=rust-lightning diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index 41c86171..9157613f 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,24 +61,34 @@ 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::ops::Deref; use crate::sign::EntropySource; use crate::io; use crate::blinded_path::BlindedPath; use crate::ln::PaymentHash; +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::offer::{Offer, OfferContents, OfferTlvStream, OfferTlvStreamRef}; +use crate::offers::invoice::BlindedPayInfo; +use crate::offers::merkle::{SignError, SignFn, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, self}; +use crate::offers::offer::{Offer, OfferContents, OfferId, OfferTlvStream, OfferTlvStreamRef}; use crate::offers::parse::{Bolt12ParseError, ParsedMessage, Bolt12SemanticError}; use crate::offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef}; use crate::offers::signer::{Metadata, MetadataMaterial}; -use crate::util::ser::{HighZeroBytesDroppedBigSize, SeekReadable, WithoutLength, Writeable, Writer}; -use crate::util::string::PrintableString; - +use crate::util::ser::{HighZeroBytesDroppedBigSize, Readable, SeekReadable, WithoutLength, Writeable, Writer}; +use crate::util::string::{PrintableString, UntrustedString}; + +#[cfg(not(c_bindings))] +use { + crate::offers::invoice::{DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder}, +}; +#[cfg(c_bindings)] +use { + crate::offers::invoice::{InvoiceWithDerivedSigningPubkeyBuilder, InvoiceWithExplicitSigningPubkeyBuilder}, +}; + +#[allow(unused_imports)] use crate::prelude::*; /// Tag for the hash function used when signing an [`InvoiceRequest`]'s merkle root. @@ -98,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. @@ -116,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, @@ -127,11 +169,14 @@ 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 + offer: &'a Offer, payer_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES, + payment_id: PaymentId, ) -> Self where ES::Target: EntropySource { let nonce = Nonce::from_entropy_source(entropy_source); - let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES); + let payment_id = Some(payment_id); + let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES, payment_id); let metadata = Metadata::Derived(derivation_material); Self { offer, @@ -141,14 +186,27 @@ 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 + offer: &'a Offer, expanded_key: &ExpandedKey, entropy_source: ES, + secp_ctx: &'b Secp256k1<$secp_context>, payment_id: PaymentId ) -> Self where ES::Target: EntropySource { let nonce = Nonce::from_entropy_source(entropy_source); - let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES); + let payment_id = Some(payment_id); + let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES, payment_id); let metadata = Metadata::DerivedSigningPubkey(derivation_material); Self { offer, @@ -158,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 { @@ -174,14 +252,22 @@ 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(mut self, network: Network) -> Result { - let chain = ChainHash::using_genesis_block(network); - if !self.offer.supports_chain(chain) { + 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 + /// [`Network::Bitcoin`] is assumed. Errors if the chain for `network` is not supported by the + /// offer. + /// + /// Successive calls to this method will override the previous setting. + 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 @@ -190,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_keys() { - tlv_stream.2.payer_id = self.payer_id.as_ref(); + if !metadata.derives_payer_keys() { + 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); +} + +#[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; - fn quantity_unchecked(mut self, quantity: u64) -> Self { - self.invoice_request.quantity = Some(quantity); - self + 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, + } } } @@ -355,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 @@ -367,35 +529,57 @@ impl UnsignedInvoiceRequest { let mut bytes = Vec::new(); unsigned_tlv_stream.write(&mut bytes).unwrap(); - let tagged_hash = TaggedHash::new(SIGNATURE_TAG, &bytes); + let tagged_hash = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &bytes); Self { bytes, contents, tagged_hash } } + /// Returns the [`TaggedHash`] of the invoice to sign. + 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 { @@ -419,6 +603,27 @@ pub struct InvoiceRequest { signature: Signature, } +/// An [`InvoiceRequest`] that has been verified by [`InvoiceRequest::verify`] and exposes different +/// ways to respond depending on whether the signing keys were derived. +#[derive(Clone, Debug)] +pub struct VerifiedInvoiceRequest { + /// The identifier of the [`Offer`] for which the [`InvoiceRequest`] was made. + pub offer_id: OfferId, + + /// The verified request. + inner: InvoiceRequest, + + /// Keys used for signing a [`Bolt12Invoice`] if they can be derived. + /// + /// If `Some`, must call [`respond_using_derived_keys`] when responding. Otherwise, call + /// [`respond_with`]. + /// + /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice + /// [`respond_using_derived_keys`]: Self::respond_using_derived_keys + /// [`respond_with`]: Self::respond_with + pub keys: Option, +} + /// The contents of an [`InvoiceRequest`], which may be shared with an [`Bolt12Invoice`]. /// /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice @@ -490,35 +695,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. @@ -537,41 +732,107 @@ impl InvoiceRequest { /// /// Errors if the request contains unknown required features. /// - /// This is not exported to bindings users as builder patterns don't map outside of move semantics. + /// # Note + /// + /// If the originating [`Offer`] was created using [`OfferBuilder::deriving_signing_pubkey`], + /// then use [`InvoiceRequest::verify`] and [`VerifiedInvoiceRequest`] methods instead. /// /// [`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< + #[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 (offer_id, keys) = $self.contents.inner.offer.verify(&$self.bytes, key, secp_ctx)?; + Ok(VerifiedInvoiceRequest { + offer_id, + #[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(); + let signature_tlv_stream = SignatureTlvStreamRef { + signature: Some(&self.signature), + }; + (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, signature_tlv_stream) + } +} + +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 verify_and_respond_using_derived_keys( - &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash, - expanded_key: &ExpandedKey, secp_ctx: &Secp256k1 - ) -> Result, Bolt12SemanticError> { + pub fn respond_using_derived_keys( + &$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.verify_and_respond_using_derived_keys_no_std( - payment_paths, payment_hash, created_at, expanded_key, secp_ctx - ) + $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 @@ -580,45 +841,54 @@ impl InvoiceRequest { /// /// 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 verify_and_respond_using_derived_keys_no_std( - &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash, - created_at: core::time::Duration, expanded_key: &ExpandedKey, secp_ctx: &Secp256k1 - ) -> Result, Bolt12SemanticError> { - if self.invoice_request_features().requires_unknown_bits() { + pub fn respond_using_derived_keys_no_std( + &$self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash, + created_at: core::time::Duration + ) -> Result<$builder, Bolt12SemanticError> { + if $self.inner.invoice_request_features().requires_unknown_bits() { return Err(Bolt12SemanticError::UnknownRequiredFeatures); } - let keys = match self.verify(expanded_key, secp_ctx) { - Err(()) => return Err(Bolt12SemanticError::InvalidMetadata), - Ok(None) => return Err(Bolt12SemanticError::InvalidMetadata), - Ok(Some(keys)) => keys, + let keys = match $self.keys { + None => return Err(Bolt12SemanticError::InvalidMetadata), + Some(keys) => keys, }; - InvoiceBuilder::for_offer_using_keys(self, payment_paths, created_at, payment_hash, keys) - } - - /// Verifies that the request was for an offer created using the given key. Returns the derived - /// keys need to sign an [`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 - ) -> Result, ()> { - self.contents.inner.offer.verify(&self.bytes, key, secp_ctx) + <$builder>::for_offer_using_keys( + &$self.inner, payment_paths, created_at, payment_hash, keys + ) } +} } - #[cfg(test)] - fn as_tlv_stream(&self) -> FullInvoiceRequestTlvStreamRef { - let (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream) = - self.contents.as_tlv_stream(); - let signature_tlv_stream = SignatureTlvStreamRef { - signature: Some(&self.signature), - }; - (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, signature_tlv_stream) +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); + + pub(crate) fn fields(&self) -> InvoiceRequestFields { + let InvoiceRequestContents { + payer_id, + inner: InvoiceRequestContentsWithoutPayerId { + payer: _, offer: _, chain: _, amount_msats, features, quantity, payer_note + }, + } = &self.inner.contents; + + InvoiceRequestFields { + payer_id: *payer_id, + amount_msats: *amount_msats, + features: features.clone(), + quantity: *quantity, + payer_note_truncated: payer_note.clone() + .map(|mut s| { s.truncate(PAYER_NOTE_LIMIT); UntrustedString(s) }), + } } } @@ -628,7 +898,7 @@ impl InvoiceRequestContents { } pub(super) fn derives_keys(&self) -> bool { - self.inner.payer.0.derives_keys() + self.inner.payer.0.derives_payer_keys() } pub(super) fn chain(&self) -> ChainHash { @@ -735,7 +1005,6 @@ tlv_stream!(InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef, INVOICE_REQUEST type FullInvoiceRequestTlvStream = (PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, SignatureTlvStream); -#[cfg(test)] type FullInvoiceRequestTlvStreamRef<'a> = ( PayerTlvStreamRef<'a>, OfferTlvStreamRef<'a>, @@ -775,7 +1044,7 @@ impl TryFrom> for UnsignedInvoiceRequest { (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream) )?; - let tagged_hash = TaggedHash::new(SIGNATURE_TAG, &bytes); + let tagged_hash = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &bytes); Ok(UnsignedInvoiceRequest { bytes, contents, tagged_hash }) } @@ -799,8 +1068,8 @@ impl TryFrom> for InvoiceRequest { None => return Err(Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingSignature)), Some(signature) => signature, }; - let message = TaggedHash::new(SIGNATURE_TAG, &bytes); - merkle::verify_signature(&signature, message, contents.payer_id)?; + let message = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &bytes); + merkle::verify_signature(&signature, &message, contents.payer_id)?; Ok(InvoiceRequest { bytes, contents, signature }) } @@ -849,29 +1118,96 @@ impl TryFrom for InvoiceRequestContents { } } +/// Fields sent in an [`InvoiceRequest`] message to include in [`PaymentContext::Bolt12Offer`]. +/// +/// [`PaymentContext::Bolt12Offer`]: crate::blinded_path::payment::PaymentContext::Bolt12Offer +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct InvoiceRequestFields { + /// A possibly transient pubkey used to sign the invoice request. + pub payer_id: PublicKey, + + /// The amount to pay in msats (i.e., the minimum lightning-payable unit for [`chain`]), which + /// must be greater than or equal to [`Offer::amount`], converted if necessary. + /// + /// [`chain`]: InvoiceRequest::chain + pub amount_msats: Option, + + /// Features pertaining to requesting an invoice. + pub features: InvoiceRequestFeatures, + + /// The quantity of the offer's item conforming to [`Offer::is_valid_quantity`]. + pub quantity: Option, + + /// A payer-provided note which will be seen by the recipient and reflected back in the invoice + /// response. Truncated to [`PAYER_NOTE_LIMIT`] characters. + pub payer_note_truncated: Option, +} + +/// The maximum number of characters included in [`InvoiceRequestFields::payer_note_truncated`]. +pub const PAYER_NOTE_LIMIT: usize = 512; + +impl Writeable for InvoiceRequestFields { + fn write(&self, writer: &mut W) -> Result<(), io::Error> { + write_tlv_fields!(writer, { + (0, self.payer_id, required), + (2, self.amount_msats.map(|v| HighZeroBytesDroppedBigSize(v)), option), + (4, WithoutLength(&self.features), required), + (6, self.quantity.map(|v| HighZeroBytesDroppedBigSize(v)), option), + (8, self.payer_note_truncated.as_ref().map(|s| WithoutLength(&s.0)), option), + }); + Ok(()) + } +} + +impl Readable for InvoiceRequestFields { + fn read(reader: &mut R) -> Result { + _init_and_read_len_prefixed_tlv_fields!(reader, { + (0, payer_id, required), + (2, amount_msats, (option, encoding: (u64, HighZeroBytesDroppedBigSize))), + (4, features, (option, encoding: (InvoiceRequestFeatures, WithoutLength))), + (6, quantity, (option, encoding: (u64, HighZeroBytesDroppedBigSize))), + (8, payer_note_truncated, (option, encoding: (String, WithoutLength))), + }); + let features = features.unwrap_or(InvoiceRequestFeatures::empty()); + + Ok(InvoiceRequestFields { + payer_id: payer_id.0.unwrap(), amount_msats, features, quantity, + payer_note_truncated: payer_note_truncated.map(|s| UntrustedString(s)), + }) + } +} + #[cfg(test)] mod tests { - use super::{InvoiceRequest, InvoiceRequestTlvStreamRef, SIGNATURE_TAG, UnsignedInvoiceRequest}; + use super::{InvoiceRequest, InvoiceRequestFields, InvoiceRequestTlvStreamRef, PAYER_NOTE_LIMIT, SIGNATURE_TAG, UnsignedInvoiceRequest}; use bitcoin::blockdata::constants::ChainHash; use bitcoin::network::constants::Network; use bitcoin::secp256k1::{KeyPair, Secp256k1, SecretKey, self}; - use core::convert::{Infallible, TryFrom}; use core::num::NonZeroU64; #[cfg(feature = "std")] use core::time::Duration; use crate::sign::KeyMaterial; + use crate::ln::channelmanager::PaymentId; use crate::ln::features::{InvoiceRequestFeatures, OfferFeatures}; use crate::ln::inbound_payment::ExpandedKey; 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::*; - use crate::util::ser::{BigSize, Writeable}; - use crate::util::string::PrintableString; + use crate::util::ser::{BigSize, Readable, Writeable}; + use crate::util::string::{PrintableString, UntrustedString}; #[test] fn builds_invoice_request_with_defaults() { @@ -880,6 +1216,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(); @@ -935,8 +1273,8 @@ mod tests { assert_eq!(invoice_request.payer_id(), payer_pubkey()); assert_eq!(invoice_request.payer_note(), None); - let message = TaggedHash::new(SIGNATURE_TAG, &invoice_request.bytes); - assert!(merkle::verify_signature(&invoice_request.signature, message, payer_pubkey()).is_ok()); + let message = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &invoice_request.bytes); + assert!(merkle::verify_signature(&invoice_request.signature, &message, payer_pubkey()).is_ok()); assert_eq!( invoice_request.as_tlv_stream(), @@ -1006,12 +1344,13 @@ mod tests { let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); let entropy = FixedEntropy {}; let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); let offer = OfferBuilder::new("foo".into(), recipient_pubkey()) .amount_msats(1000) .build().unwrap(); let invoice_request = offer - .request_invoice_deriving_metadata(payer_id, &expanded_key, &entropy) + .request_invoice_deriving_metadata(payer_id, &expanded_key, &entropy, payment_id) .unwrap() .build().unwrap() .sign(payer_sign).unwrap(); @@ -1021,7 +1360,10 @@ mod tests { .unwrap() .build().unwrap() .sign(recipient_sign).unwrap(); - assert!(invoice.verify(&expanded_key, &secp_ctx)); + match invoice.verify(&expanded_key, &secp_ctx) { + Ok(payment_id) => assert_eq!(payment_id, PaymentId([1; 32])), + Err(()) => panic!("verification failed"), + } // Fails verification with altered fields let ( @@ -1036,7 +1378,7 @@ mod tests { let mut bytes = Vec::new(); tlv_stream.write(&mut bytes).unwrap(); - let message = TaggedHash::new(INVOICE_SIGNATURE_TAG, &bytes); + let message = TaggedHash::from_valid_tlv_stream_bytes(INVOICE_SIGNATURE_TAG, &bytes); let signature = merkle::sign_message(recipient_sign, &message, recipient_pubkey()).unwrap(); signature_tlv_stream.signature = Some(&signature); @@ -1044,7 +1386,7 @@ mod tests { signature_tlv_stream.write(&mut encoded_invoice).unwrap(); let invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap(); - assert!(!invoice.verify(&expanded_key, &secp_ctx)); + assert!(invoice.verify(&expanded_key, &secp_ctx).is_err()); // Fails verification with altered metadata let ( @@ -1059,7 +1401,7 @@ mod tests { let mut bytes = Vec::new(); tlv_stream.write(&mut bytes).unwrap(); - let message = TaggedHash::new(INVOICE_SIGNATURE_TAG, &bytes); + let message = TaggedHash::from_valid_tlv_stream_bytes(INVOICE_SIGNATURE_TAG, &bytes); let signature = merkle::sign_message(recipient_sign, &message, recipient_pubkey()).unwrap(); signature_tlv_stream.signature = Some(&signature); @@ -1067,7 +1409,7 @@ mod tests { signature_tlv_stream.write(&mut encoded_invoice).unwrap(); let invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap(); - assert!(!invoice.verify(&expanded_key, &secp_ctx)); + assert!(invoice.verify(&expanded_key, &secp_ctx).is_err()); } #[test] @@ -1075,12 +1417,13 @@ mod tests { let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); let entropy = FixedEntropy {}; let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); let offer = OfferBuilder::new("foo".into(), recipient_pubkey()) .amount_msats(1000) .build().unwrap(); let invoice_request = offer - .request_invoice_deriving_payer_id(&expanded_key, &entropy, &secp_ctx) + .request_invoice_deriving_payer_id(&expanded_key, &entropy, &secp_ctx, payment_id) .unwrap() .build_and_sign() .unwrap(); @@ -1089,7 +1432,10 @@ mod tests { .unwrap() .build().unwrap() .sign(recipient_sign).unwrap(); - assert!(invoice.verify(&expanded_key, &secp_ctx)); + match invoice.verify(&expanded_key, &secp_ctx) { + Ok(payment_id) => assert_eq!(payment_id, PaymentId([1; 32])), + Err(()) => panic!("verification failed"), + } // Fails verification with altered fields let ( @@ -1104,7 +1450,7 @@ mod tests { let mut bytes = Vec::new(); tlv_stream.write(&mut bytes).unwrap(); - let message = TaggedHash::new(INVOICE_SIGNATURE_TAG, &bytes); + let message = TaggedHash::from_valid_tlv_stream_bytes(INVOICE_SIGNATURE_TAG, &bytes); let signature = merkle::sign_message(recipient_sign, &message, recipient_pubkey()).unwrap(); signature_tlv_stream.signature = Some(&signature); @@ -1112,7 +1458,7 @@ mod tests { signature_tlv_stream.write(&mut encoded_invoice).unwrap(); let invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap(); - assert!(!invoice.verify(&expanded_key, &secp_ctx)); + assert!(invoice.verify(&expanded_key, &secp_ctx).is_err()); // Fails verification with altered payer id let ( @@ -1127,7 +1473,7 @@ mod tests { let mut bytes = Vec::new(); tlv_stream.write(&mut bytes).unwrap(); - let message = TaggedHash::new(INVOICE_SIGNATURE_TAG, &bytes); + let message = TaggedHash::from_valid_tlv_stream_bytes(INVOICE_SIGNATURE_TAG, &bytes); let signature = merkle::sign_message(recipient_sign, &message, recipient_pubkey()).unwrap(); signature_tlv_stream.signature = Some(&signature); @@ -1135,7 +1481,7 @@ mod tests { signature_tlv_stream.write(&mut encoded_invoice).unwrap(); let invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap(); - assert!(!invoice.verify(&expanded_key, &secp_ctx)); + assert!(invoice.verify(&expanded_key, &secp_ctx).is_err()); } #[test] @@ -1467,10 +1813,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()) @@ -1881,8 +2227,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(); @@ -1897,4 +2243,55 @@ mod tests { Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::InvalidValue)), } } + + #[test] + fn copies_verified_invoice_request_fields() { + let desc = "foo".to_string(); + let node_id = recipient_pubkey(); + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let secp_ctx = Secp256k1::new(); + + #[cfg(c_bindings)] + use crate::offers::offer::OfferWithDerivedMetadataBuilder as OfferBuilder; + let offer = OfferBuilder + ::deriving_signing_pubkey(desc, node_id, &expanded_key, &entropy, &secp_ctx) + .chain(Network::Testnet) + .amount_msats(1000) + .supported_quantity(Quantity::Unbounded) + .build().unwrap(); + assert_eq!(offer.signing_pubkey(), node_id); + + let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .chain(Network::Testnet).unwrap() + .amount_msats(1001).unwrap() + .quantity(1).unwrap() + .payer_note("0".repeat(PAYER_NOTE_LIMIT * 2)) + .build().unwrap() + .sign(payer_sign).unwrap(); + match invoice_request.verify(&expanded_key, &secp_ctx) { + Ok(invoice_request) => { + let fields = invoice_request.fields(); + assert_eq!(invoice_request.offer_id, offer.id()); + assert_eq!( + fields, + InvoiceRequestFields { + payer_id: payer_pubkey(), + amount_msats: Some(1001), + features: InvoiceRequestFeatures::empty(), + quantity: Some(1), + payer_note_truncated: Some(UntrustedString("0".repeat(PAYER_NOTE_LIMIT))), + } + ); + + let mut buffer = Vec::new(); + fields.write(&mut buffer).unwrap(); + + let deserialized_fields: InvoiceRequestFields = + Readable::read(&mut buffer.as_slice()).unwrap(); + assert_eq!(deserialized_fields, fields); + }, + Err(_) => panic!("unexpected error"), + } + } }