X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Foffers%2Finvoice_request.rs;h=bd6d58371a9f316cccf6b362cc4825f27171f177;hb=a1a2f2abc86b0015f05ca69b22ce27380d4d90aa;hp=b05f9af8481bff6ed333856cc74e7c28e5f43d59;hpb=230f081e5a63161c3396adfb509ccfe045e921f8;p=rust-lightning diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index b05f9af8..bd6d5837 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -64,6 +64,7 @@ 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; @@ -128,10 +129,12 @@ impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, ExplicitPayerI } 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, @@ -145,10 +148,12 @@ impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, ExplicitPayerI impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, DerivedPayerId, T> { 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, 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, @@ -174,8 +179,16 @@ 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); + pub fn chain(self, network: Network) -> Result { + 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(mut self, chain: ChainHash) -> Result { if !self.offer.supports_chain(chain) { return Err(Bolt12SemanticError::UnsupportedChain); } @@ -259,7 +272,7 @@ impl<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signing> InvoiceRequestBuilder<'a 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() { + if !metadata.derives_payer_keys() { tlv_stream.2.payer_id = self.payer_id.as_ref(); } @@ -372,6 +385,11 @@ impl UnsignedInvoiceRequest { Self { bytes, contents, tagged_hash } } + /// Returns the [`TaggedHash`] of the invoice to sign. + pub fn tagged_hash(&self) -> &TaggedHash { + &self.tagged_hash + } + /// Signs the [`TaggedHash`] of the invoice request using the given function. /// /// Note: The hash computation may have included unknown, odd TLV records. @@ -419,6 +437,24 @@ 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 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 @@ -441,49 +477,58 @@ pub(super) struct InvoiceRequestContentsWithoutPayerId { payer_note: Option, } -impl InvoiceRequest { +macro_rules! invoice_request_accessors { ($self: ident, $contents: expr) => { /// An unpredictable series of bytes, typically containing information about the derivation of /// [`payer_id`]. /// /// [`payer_id`]: Self::payer_id - pub fn metadata(&self) -> &[u8] { - self.contents.metadata() + pub fn payer_metadata(&$self) -> &[u8] { + $contents.metadata() } /// A chain from [`Offer::chains`] that the offer is valid for. - pub fn chain(&self) -> ChainHash { - self.contents.chain() + pub fn chain(&$self) -> ChainHash { + $contents.chain() } /// 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`]: Self::chain - pub fn amount_msats(&self) -> Option { - self.contents.inner.amount_msats + pub fn amount_msats(&$self) -> Option { + $contents.amount_msats() } /// Features pertaining to requesting an invoice. - pub fn features(&self) -> &InvoiceRequestFeatures { - &self.contents.inner.features + pub fn invoice_request_features(&$self) -> &InvoiceRequestFeatures { + &$contents.features() } /// The quantity of the offer's item conforming to [`Offer::is_valid_quantity`]. - pub fn quantity(&self) -> Option { - self.contents.inner.quantity + pub fn quantity(&$self) -> Option { + $contents.quantity() } /// A possibly transient pubkey used to sign the invoice request. - pub fn payer_id(&self) -> PublicKey { - self.contents.payer_id + pub fn payer_id(&$self) -> PublicKey { + $contents.payer_id() } /// A payer-provided note which will be seen by the recipient and reflected back in the invoice /// response. - pub fn payer_note(&self) -> Option { - self.contents.inner.payer_note.as_ref() - .map(|payer_note| PrintableString(payer_note.as_str())) + pub fn payer_note(&$self) -> Option { + $contents.payer_note() } +} } + +impl UnsignedInvoiceRequest { + offer_accessors!(self, self.contents.inner.offer); + 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`]. /// @@ -528,20 +573,83 @@ impl InvoiceRequest { /// /// Errors if the request contains unknown required features. /// + /// # Note + /// + /// 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, created_at: core::time::Duration ) -> Result, Bolt12SemanticError> { - if self.features().requires_unknown_bits() { + if self.invoice_request_features().requires_unknown_bits() { return Err(Bolt12SemanticError::UnknownRequiredFeatures); } InvoiceBuilder::for_offer(self, payment_paths, created_at, payment_hash) } + /// 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 + ) -> Result { + let keys = self.contents.inner.offer.verify(&self.bytes, key, secp_ctx)?; + Ok(VerifiedInvoiceRequest { + inner: self, + 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); + + /// 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) + } + /// 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. @@ -552,17 +660,14 @@ impl InvoiceRequest { /// /// [`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 + pub fn respond_using_derived_keys( + &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash ) -> Result, 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 @@ -574,42 +679,22 @@ impl InvoiceRequest { /// 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( + pub fn 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 + created_at: core::time::Duration ) -> Result, Bolt12SemanticError> { - if self.features().requires_unknown_bits() { + 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) - } - - #[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) + InvoiceBuilder::for_offer_using_keys( + &self.inner, payment_paths, created_at, payment_hash, keys + ) } } @@ -619,17 +704,34 @@ 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 { self.inner.chain() } + pub(super) fn amount_msats(&self) -> Option { + self.inner.amount_msats + } + + pub(super) fn features(&self) -> &InvoiceRequestFeatures { + &self.inner.features + } + + pub(super) fn quantity(&self) -> Option { + self.inner.quantity + } + pub(super) fn payer_id(&self) -> PublicKey { self.payer_id } + pub(super) fn payer_note(&self) -> Option { + self.inner.payer_note.as_ref() + .map(|payer_note| PrintableString(payer_note.as_str())) + } + pub(super) fn as_tlv_stream(&self) -> PartialInvoiceRequestTlvStreamRef { let (payer, offer, mut invoice_request) = self.inner.as_tlv_stream(); invoice_request.payer_id = Some(&self.payer_id); @@ -773,7 +875,8 @@ impl TryFrom> for InvoiceRequest { None => return Err(Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingSignature)), Some(signature) => signature, }; - merkle::verify_signature(&signature, SIGNATURE_TAG, &bytes, contents.payer_id)?; + let message = TaggedHash::new(SIGNATURE_TAG, &bytes); + merkle::verify_signature(&signature, message, contents.payer_id)?; Ok(InvoiceRequest { bytes, contents, signature }) } @@ -834,7 +937,8 @@ mod tests { #[cfg(feature = "std")] use core::time::Duration; use crate::sign::KeyMaterial; - use crate::ln::features::InvoiceRequestFeatures; + 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}; @@ -857,6 +961,25 @@ mod tests { let mut buffer = Vec::new(); unsigned_invoice_request.write(&mut buffer).unwrap(); + assert_eq!(unsigned_invoice_request.bytes, buffer.as_slice()); + assert_eq!(unsigned_invoice_request.payer_metadata(), &[1; 32]); + assert_eq!(unsigned_invoice_request.chains(), vec![ChainHash::using_genesis_block(Network::Bitcoin)]); + assert_eq!(unsigned_invoice_request.metadata(), None); + assert_eq!(unsigned_invoice_request.amount(), Some(&Amount::Bitcoin { amount_msats: 1000 })); + assert_eq!(unsigned_invoice_request.description(), PrintableString("foo")); + assert_eq!(unsigned_invoice_request.offer_features(), &OfferFeatures::empty()); + assert_eq!(unsigned_invoice_request.absolute_expiry(), None); + assert_eq!(unsigned_invoice_request.paths(), &[]); + assert_eq!(unsigned_invoice_request.issuer(), None); + assert_eq!(unsigned_invoice_request.supported_quantity(), Quantity::One); + assert_eq!(unsigned_invoice_request.signing_pubkey(), recipient_pubkey()); + assert_eq!(unsigned_invoice_request.chain(), ChainHash::using_genesis_block(Network::Bitcoin)); + assert_eq!(unsigned_invoice_request.amount_msats(), None); + assert_eq!(unsigned_invoice_request.invoice_request_features(), &InvoiceRequestFeatures::empty()); + assert_eq!(unsigned_invoice_request.quantity(), None); + assert_eq!(unsigned_invoice_request.payer_id(), payer_pubkey()); + assert_eq!(unsigned_invoice_request.payer_note(), None); + match UnsignedInvoiceRequest::try_from(buffer) { Err(e) => panic!("error parsing unsigned invoice request: {:?}", e), Ok(parsed) => { @@ -871,18 +994,26 @@ mod tests { invoice_request.write(&mut buffer).unwrap(); assert_eq!(invoice_request.bytes, buffer.as_slice()); - assert_eq!(invoice_request.metadata(), &[1; 32]); + assert_eq!(invoice_request.payer_metadata(), &[1; 32]); + assert_eq!(invoice_request.chains(), vec![ChainHash::using_genesis_block(Network::Bitcoin)]); + assert_eq!(invoice_request.metadata(), None); + assert_eq!(invoice_request.amount(), Some(&Amount::Bitcoin { amount_msats: 1000 })); + assert_eq!(invoice_request.description(), PrintableString("foo")); + assert_eq!(invoice_request.offer_features(), &OfferFeatures::empty()); + assert_eq!(invoice_request.absolute_expiry(), None); + assert_eq!(invoice_request.paths(), &[]); + assert_eq!(invoice_request.issuer(), None); + assert_eq!(invoice_request.supported_quantity(), Quantity::One); + assert_eq!(invoice_request.signing_pubkey(), recipient_pubkey()); assert_eq!(invoice_request.chain(), ChainHash::using_genesis_block(Network::Bitcoin)); assert_eq!(invoice_request.amount_msats(), None); - assert_eq!(invoice_request.features(), &InvoiceRequestFeatures::empty()); + assert_eq!(invoice_request.invoice_request_features(), &InvoiceRequestFeatures::empty()); assert_eq!(invoice_request.quantity(), None); assert_eq!(invoice_request.payer_id(), payer_pubkey()); assert_eq!(invoice_request.payer_note(), None); - assert!( - merkle::verify_signature( - &invoice_request.signature, SIGNATURE_TAG, &invoice_request.bytes, payer_pubkey() - ).is_ok() - ); + + let message = TaggedHash::new(SIGNATURE_TAG, &invoice_request.bytes); + assert!(merkle::verify_signature(&invoice_request.signature, message, payer_pubkey()).is_ok()); assert_eq!( invoice_request.as_tlv_stream(), @@ -952,12 +1083,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(); @@ -967,7 +1099,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 ( @@ -990,7 +1125,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 ( @@ -1013,7 +1148,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] @@ -1021,12 +1156,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(); @@ -1035,7 +1171,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 ( @@ -1058,7 +1197,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 ( @@ -1081,7 +1220,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] @@ -1275,7 +1414,7 @@ mod tests { .build().unwrap() .sign(payer_sign).unwrap(); let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); - assert_eq!(invoice_request.features(), &InvoiceRequestFeatures::unknown()); + assert_eq!(invoice_request.invoice_request_features(), &InvoiceRequestFeatures::unknown()); assert_eq!(tlv_stream.features, Some(&InvoiceRequestFeatures::unknown())); let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) @@ -1287,7 +1426,7 @@ mod tests { .build().unwrap() .sign(payer_sign).unwrap(); let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); - assert_eq!(invoice_request.features(), &InvoiceRequestFeatures::empty()); + assert_eq!(invoice_request.invoice_request_features(), &InvoiceRequestFeatures::empty()); assert_eq!(tlv_stream.features, None); }