X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Foffers%2Finvoice_request.rs;h=f014bf120021b52b613a6726f988e8fb6b076f1b;hb=d7e3320c030654ca3ff2b6ffd83ae65dfbe5e3c8;hp=26151dfbd18500bf2392c89a2b900c945d9896a6;hpb=3880e69237d7f6a33db1912176de5a745ea99b41;p=rust-lightning diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index 26151dfb..f014bf12 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -11,12 +11,12 @@ //! //! An [`InvoiceRequest`] can be built from a parsed [`Offer`] as an "offer to be paid". It is //! typically constructed by a customer and sent to the merchant who had published the corresponding -//! offer. The recipient of the request responds with an [`Invoice`]. +//! offer. The recipient of the request responds with a [`Bolt12Invoice`]. //! //! For an "offer for money" (e.g., refund, ATM withdrawal), where an offer doesn't exist as a //! precursor, see [`Refund`]. //! -//! [`Invoice`]: crate::offers::invoice::Invoice +//! [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice //! [`Refund`]: crate::offers::refund::Refund //! //! ``` @@ -30,7 +30,7 @@ //! use lightning::offers::offer::Offer; //! use lightning::util::ser::Writeable; //! -//! # fn parse() -> Result<(), lightning::offers::parse::ParseError> { +//! # fn parse() -> Result<(), lightning::offers::parse::Bolt12ParseError> { //! let secp_ctx = Secp256k1::new(); //! let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32])?); //! let pubkey = PublicKey::from(keys); @@ -58,19 +58,19 @@ use bitcoin::secp256k1::{KeyPair, Message, PublicKey, Secp256k1, self}; use bitcoin::secp256k1::schnorr::Signature; use core::convert::{Infallible, TryFrom}; use core::ops::Deref; -use crate::chain::keysinterface::EntropySource; +use crate::sign::EntropySource; use crate::io; +use crate::blinded_path::BlindedPath; use crate::ln::PaymentHash; use crate::ln::features::InvoiceRequestFeatures; use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce}; use crate::ln::msgs::DecodeError; -use crate::offers::invoice::{BlindedPayInfo, InvoiceBuilder}; -use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, TlvStream, self}; +use crate::offers::invoice::{BlindedPayInfo, DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder}; +use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, self}; use crate::offers::offer::{Offer, OfferContents, OfferTlvStream, OfferTlvStreamRef}; -use crate::offers::parse::{ParseError, ParsedMessage, SemanticError}; +use crate::offers::parse::{Bolt12ParseError, ParsedMessage, Bolt12SemanticError}; use crate::offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef}; use crate::offers::signer::{Metadata, MetadataMaterial}; -use crate::onion_message::BlindedPath; use crate::util::ser::{HighZeroBytesDroppedBigSize, SeekReadable, WithoutLength, Writeable, Writer}; use crate::util::string::PrintableString; @@ -78,12 +78,14 @@ use crate::prelude::*; const SIGNATURE_TAG: &'static str = concat!("lightning", "invoice_request", "signature"); -const IV_BYTES: &[u8; IV_LEN] = b"LDK Invreq ~~~~~"; +pub(super) const IV_BYTES: &[u8; IV_LEN] = b"LDK Invreq ~~~~~"; /// Builds an [`InvoiceRequest`] from 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 pub struct InvoiceRequestBuilder<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signing> { offer: &'a Offer, @@ -94,12 +96,18 @@ pub struct InvoiceRequestBuilder<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signi } /// 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. pub trait PayerIdStrategy {} /// [`InvoiceRequest::payer_id`] will be explicitly set. +/// +/// This is not exported to bindings users as builder patterns don't map outside of move semantics. pub struct ExplicitPayerId {} /// [`InvoiceRequest::payer_id`] will be derived. +/// +/// This is not exported to bindings users as builder patterns don't map outside of move semantics. pub struct DerivedPayerId {} impl PayerIdStrategy for ExplicitPayerId {} @@ -163,10 +171,10 @@ 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 { + pub fn chain(mut self, network: Network) -> Result { let chain = ChainHash::using_genesis_block(network); if !self.offer.supports_chain(chain) { - return Err(SemanticError::UnsupportedChain); + return Err(Bolt12SemanticError::UnsupportedChain); } self.invoice_request.chain = Some(chain); @@ -179,7 +187,7 @@ 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 { + 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 )?; @@ -191,7 +199,7 @@ impl<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signing> InvoiceRequestBuilder<'a /// 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 { + pub fn quantity(mut self, quantity: u64) -> Result { self.invoice_request.offer.check_quantity(Some(quantity))?; self.invoice_request.quantity = Some(quantity); Ok(self) @@ -207,17 +215,17 @@ impl<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signing> InvoiceRequestBuilder<'a fn build_with_checks(mut self) -> Result< (UnsignedInvoiceRequest<'a>, Option, Option<&'b Secp256k1>), - SemanticError + Bolt12SemanticError > { #[cfg(feature = "std")] { if self.offer.is_expired() { - return Err(SemanticError::AlreadyExpired); + return Err(Bolt12SemanticError::AlreadyExpired); } } let chain = self.invoice_request.chain(); if !self.offer.supports_chain(chain) { - return Err(SemanticError::UnsupportedChain); + return Err(Bolt12SemanticError::UnsupportedChain); } if chain == self.offer.implied_chain() { @@ -225,7 +233,7 @@ impl<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signing> InvoiceRequestBuilder<'a } if self.offer.amount().is_none() && self.invoice_request.amount_msats.is_none() { - return Err(SemanticError::MissingAmount); + return Err(Bolt12SemanticError::MissingAmount); } self.invoice_request.offer.check_quantity(self.invoice_request.quantity)?; @@ -239,7 +247,7 @@ impl<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signing> InvoiceRequestBuilder<'a fn build_without_checks(mut self) -> (UnsignedInvoiceRequest<'a>, Option, Option<&'b Secp256k1>) { - // Create the metadata for stateless verification of an Invoice. + // 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() { @@ -282,7 +290,7 @@ impl<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signing> InvoiceRequestBuilder<'a impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, ExplicitPayerId, T> { /// Builds an unsigned [`InvoiceRequest`] after checking for valid semantics. It can be signed /// by [`UnsignedInvoiceRequest::sign`]. - pub fn build(self) -> Result, SemanticError> { + pub fn build(self) -> Result, Bolt12SemanticError> { let (unsigned_invoice_request, keys, _) = self.build_with_checks()?; debug_assert!(keys.is_none()); Ok(unsigned_invoice_request) @@ -291,7 +299,7 @@ impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, ExplicitPayerI 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 { + pub fn build_and_sign(self) -> Result { let (unsigned_invoice_request, keys, secp_ctx) = self.build_with_checks()?; debug_assert!(keys.is_some()); @@ -340,6 +348,8 @@ pub struct UnsignedInvoiceRequest<'a> { impl<'a> UnsignedInvoiceRequest<'a> { /// Signs the invoice request using the given function. + /// + /// This is not exported to bindings users as functions are not yet mapped. pub fn sign(self, sign: F) -> Result> where F: FnOnce(&Message) -> Result @@ -371,12 +381,12 @@ impl<'a> UnsignedInvoiceRequest<'a> { } } -/// An `InvoiceRequest` is a request for an [`Invoice`] formulated from an [`Offer`]. +/// An `InvoiceRequest` is a request for a [`Bolt12Invoice`] formulated from an [`Offer`]. /// /// An offer may provide choices such as quantity, amount, chain, features, etc. An invoice request /// specifies these such that its recipient can send an invoice for payment. /// -/// [`Invoice`]: crate::offers::invoice::Invoice +/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice /// [`Offer`]: crate::offers::offer::Offer #[derive(Clone, Debug)] #[cfg_attr(test, derive(PartialEq))] @@ -386,9 +396,9 @@ pub struct InvoiceRequest { signature: Signature, } -/// The contents of an [`InvoiceRequest`], which may be shared with an [`Invoice`]. +/// The contents of an [`InvoiceRequest`], which may be shared with an [`Bolt12Invoice`]. /// -/// [`Invoice`]: crate::offers::invoice::Invoice +/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice #[derive(Clone, Debug)] #[cfg_attr(test, derive(PartialEq))] pub(super) struct InvoiceRequestContents { @@ -459,18 +469,19 @@ impl InvoiceRequest { self.signature } - /// Creates an [`Invoice`] for the request with the given required fields and using the + /// 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. /// - /// [`Invoice`]: crate::offers::invoice::Invoice + /// 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<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash - ) -> Result { + &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"); @@ -478,11 +489,11 @@ impl InvoiceRequest { self.respond_with_no_std(payment_paths, payment_hash, created_at) } - /// Creates an [`Invoice`] for the request with the given required fields. + /// Creates an [`InvoiceBuilder`] for the request with the given required fields. /// /// Unless [`InvoiceBuilder::relative_expiry`] is set, the invoice will expire two hours after - /// `created_at`, which is used to set [`Invoice::created_at`]. Useful for `no-std` builds where - /// [`std::time::SystemTime`] is not available. + /// `created_at`, which is used to set [`Bolt12Invoice::created_at`]. Useful for `no-std` builds + /// where [`std::time::SystemTime`] is not available. /// /// The caller is expected to remember the preimage of `payment_hash` in order to claim a payment /// for the invoice. @@ -494,24 +505,78 @@ impl InvoiceRequest { /// /// Errors if the request contains unknown required features. /// - /// [`Invoice`]: crate::offers::invoice::Invoice - /// [`Invoice::created_at`]: crate::offers::invoice::Invoice::created_at + /// 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 pub fn respond_with_no_std( - &self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash, + &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash, created_at: core::time::Duration - ) -> Result { + ) -> Result, Bolt12SemanticError> { if self.features().requires_unknown_bits() { - return Err(SemanticError::UnknownRequiredFeatures); + 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. + /// 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> { + 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 + ) + } + + /// 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_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.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, + }; + + 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 - ) -> bool { - self.contents.inner.offer.verify(TlvStream::new(&self.bytes), key, secp_ctx) + ) -> Result, ()> { + self.contents.inner.offer.verify(&self.bytes, key, secp_ctx) } #[cfg(test)] @@ -530,10 +595,18 @@ impl InvoiceRequestContents { self.inner.metadata() } + pub(super) fn derives_keys(&self) -> bool { + self.inner.payer.0.derives_keys() + } + pub(super) fn chain(&self) -> ChainHash { self.inner.chain() } + pub(super) fn payer_id(&self) -> PublicKey { + self.payer_id + } + 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); @@ -587,12 +660,20 @@ impl Writeable for InvoiceRequestContents { } } -tlv_stream!(InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef, 80..160, { +/// Valid type range for invoice_request TLV records. +pub(super) const INVOICE_REQUEST_TYPES: core::ops::Range = 80..160; + +/// TLV record type for [`InvoiceRequest::payer_id`] and [`Refund::payer_id`]. +/// +/// [`Refund::payer_id`]: crate::offers::refund::Refund::payer_id +pub(super) const INVOICE_REQUEST_PAYER_ID_TYPE: u64 = 88; + +tlv_stream!(InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef, INVOICE_REQUEST_TYPES, { (80, chain: ChainHash), (82, amount: (u64, HighZeroBytesDroppedBigSize)), (84, features: (InvoiceRequestFeatures, WithoutLength)), (86, quantity: (u64, HighZeroBytesDroppedBigSize)), - (88, payer_id: PublicKey), + (INVOICE_REQUEST_PAYER_ID_TYPE, payer_id: PublicKey), (89, payer_note: (String, WithoutLength)), }); @@ -627,7 +708,7 @@ type PartialInvoiceRequestTlvStreamRef<'a> = ( ); impl TryFrom> for InvoiceRequest { - type Error = ParseError; + type Error = Bolt12ParseError; fn try_from(bytes: Vec) -> Result { let invoice_request = ParsedMessage::::try_from(bytes)?; @@ -641,7 +722,7 @@ impl TryFrom> for InvoiceRequest { )?; let signature = match signature { - None => return Err(ParseError::InvalidSemantics(SemanticError::MissingSignature)), + None => return Err(Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingSignature)), Some(signature) => signature, }; merkle::verify_signature(&signature, SIGNATURE_TAG, &bytes, contents.payer_id)?; @@ -651,7 +732,7 @@ impl TryFrom> for InvoiceRequest { } impl TryFrom for InvoiceRequestContents { - type Error = SemanticError; + type Error = Bolt12SemanticError; fn try_from(tlv_stream: PartialInvoiceRequestTlvStream) -> Result { let ( @@ -661,17 +742,17 @@ impl TryFrom for InvoiceRequestContents { ) = tlv_stream; let payer = match metadata { - None => return Err(SemanticError::MissingPayerMetadata), + None => return Err(Bolt12SemanticError::MissingPayerMetadata), Some(metadata) => PayerContents(Metadata::Bytes(metadata)), }; let offer = OfferContents::try_from(offer_tlv_stream)?; if !offer.supports_chain(chain.unwrap_or_else(|| offer.implied_chain())) { - return Err(SemanticError::UnsupportedChain); + return Err(Bolt12SemanticError::UnsupportedChain); } if offer.amount().is_none() && amount.is_none() { - return Err(SemanticError::MissingAmount); + return Err(Bolt12SemanticError::MissingAmount); } offer.check_quantity(quantity)?; @@ -680,7 +761,7 @@ impl TryFrom for InvoiceRequestContents { let features = features.unwrap_or_else(InvoiceRequestFeatures::empty); let payer_id = match payer_id { - None => return Err(SemanticError::MissingPayerId), + None => return Err(Bolt12SemanticError::MissingPayerId), Some(payer_id) => payer_id, }; @@ -704,11 +785,14 @@ mod tests { use core::num::NonZeroU64; #[cfg(feature = "std")] use core::time::Duration; + use crate::sign::KeyMaterial; use crate::ln::features::InvoiceRequestFeatures; + 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, self}; use crate::offers::offer::{Amount, OfferBuilder, OfferTlvStreamRef, Quantity}; - use crate::offers::parse::{ParseError, SemanticError}; + use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError}; use crate::offers::payer::PayerTlvStreamRef; use crate::offers::test_utils::*; use crate::util::ser::{BigSize, Writeable}; @@ -798,10 +882,152 @@ mod tests { .build() { Ok(_) => panic!("expected error"), - Err(e) => assert_eq!(e, SemanticError::AlreadyExpired), + Err(e) => assert_eq!(e, Bolt12SemanticError::AlreadyExpired), } } + #[test] + fn builds_invoice_request_with_derived_metadata() { + let payer_id = payer_pubkey(); + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let secp_ctx = Secp256k1::new(); + + 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) + .unwrap() + .build().unwrap() + .sign(payer_sign).unwrap(); + assert_eq!(invoice_request.payer_id(), payer_pubkey()); + + let invoice = invoice_request.respond_with_no_std(payment_paths(), payment_hash(), now()) + .unwrap() + .build().unwrap() + .sign(recipient_sign).unwrap(); + assert!(invoice.verify(&expanded_key, &secp_ctx)); + + // Fails verification with altered fields + let ( + payer_tlv_stream, offer_tlv_stream, mut invoice_request_tlv_stream, + mut invoice_tlv_stream, mut signature_tlv_stream + ) = invoice.as_tlv_stream(); + invoice_request_tlv_stream.amount = Some(2000); + invoice_tlv_stream.amount = Some(2000); + + let tlv_stream = + (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream); + let mut bytes = Vec::new(); + tlv_stream.write(&mut bytes).unwrap(); + + let signature = merkle::sign_message( + recipient_sign, INVOICE_SIGNATURE_TAG, &bytes, recipient_pubkey() + ).unwrap(); + signature_tlv_stream.signature = Some(&signature); + + let mut encoded_invoice = bytes; + signature_tlv_stream.write(&mut encoded_invoice).unwrap(); + + let invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap(); + assert!(!invoice.verify(&expanded_key, &secp_ctx)); + + // Fails verification with altered metadata + let ( + mut payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream, + mut signature_tlv_stream + ) = invoice.as_tlv_stream(); + let metadata = payer_tlv_stream.metadata.unwrap().iter().copied().rev().collect(); + payer_tlv_stream.metadata = Some(&metadata); + + let tlv_stream = + (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream); + let mut bytes = Vec::new(); + tlv_stream.write(&mut bytes).unwrap(); + + let signature = merkle::sign_message( + recipient_sign, INVOICE_SIGNATURE_TAG, &bytes, recipient_pubkey() + ).unwrap(); + signature_tlv_stream.signature = Some(&signature); + + let mut encoded_invoice = bytes; + signature_tlv_stream.write(&mut encoded_invoice).unwrap(); + + let invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap(); + assert!(!invoice.verify(&expanded_key, &secp_ctx)); + } + + #[test] + fn builds_invoice_request_with_derived_payer_id() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let secp_ctx = Secp256k1::new(); + + 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) + .unwrap() + .build_and_sign() + .unwrap(); + + let invoice = invoice_request.respond_with_no_std(payment_paths(), payment_hash(), now()) + .unwrap() + .build().unwrap() + .sign(recipient_sign).unwrap(); + assert!(invoice.verify(&expanded_key, &secp_ctx)); + + // Fails verification with altered fields + let ( + payer_tlv_stream, offer_tlv_stream, mut invoice_request_tlv_stream, + mut invoice_tlv_stream, mut signature_tlv_stream + ) = invoice.as_tlv_stream(); + invoice_request_tlv_stream.amount = Some(2000); + invoice_tlv_stream.amount = Some(2000); + + let tlv_stream = + (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream); + let mut bytes = Vec::new(); + tlv_stream.write(&mut bytes).unwrap(); + + let signature = merkle::sign_message( + recipient_sign, INVOICE_SIGNATURE_TAG, &bytes, recipient_pubkey() + ).unwrap(); + signature_tlv_stream.signature = Some(&signature); + + let mut encoded_invoice = bytes; + signature_tlv_stream.write(&mut encoded_invoice).unwrap(); + + let invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap(); + assert!(!invoice.verify(&expanded_key, &secp_ctx)); + + // Fails verification with altered payer id + let ( + payer_tlv_stream, offer_tlv_stream, mut invoice_request_tlv_stream, invoice_tlv_stream, + mut signature_tlv_stream + ) = invoice.as_tlv_stream(); + let payer_id = pubkey(1); + invoice_request_tlv_stream.payer_id = Some(&payer_id); + + let tlv_stream = + (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream); + let mut bytes = Vec::new(); + tlv_stream.write(&mut bytes).unwrap(); + + let signature = merkle::sign_message( + recipient_sign, INVOICE_SIGNATURE_TAG, &bytes, recipient_pubkey() + ).unwrap(); + signature_tlv_stream.signature = Some(&signature); + + let mut encoded_invoice = bytes; + signature_tlv_stream.write(&mut encoded_invoice).unwrap(); + + let invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap(); + assert!(!invoice.verify(&expanded_key, &secp_ctx)); + } + #[test] fn builds_invoice_request_with_chain() { let mainnet = ChainHash::using_genesis_block(Network::Bitcoin); @@ -865,7 +1091,7 @@ mod tests { .chain(Network::Bitcoin) { Ok(_) => panic!("expected error"), - Err(e) => assert_eq!(e, SemanticError::UnsupportedChain), + Err(e) => assert_eq!(e, Bolt12SemanticError::UnsupportedChain), } match OfferBuilder::new("foo".into(), recipient_pubkey()) @@ -876,7 +1102,7 @@ mod tests { .build() { Ok(_) => panic!("expected error"), - Err(e) => assert_eq!(e, SemanticError::UnsupportedChain), + Err(e) => assert_eq!(e, Bolt12SemanticError::UnsupportedChain), } } @@ -923,7 +1149,7 @@ mod tests { .amount_msats(999) { Ok(_) => panic!("expected error"), - Err(e) => assert_eq!(e, SemanticError::InsufficientAmount), + Err(e) => assert_eq!(e, Bolt12SemanticError::InsufficientAmount), } match OfferBuilder::new("foo".into(), recipient_pubkey()) @@ -935,7 +1161,7 @@ mod tests { .amount_msats(1000) { Ok(_) => panic!("expected error"), - Err(e) => assert_eq!(e, SemanticError::InsufficientAmount), + Err(e) => assert_eq!(e, Bolt12SemanticError::InsufficientAmount), } match OfferBuilder::new("foo".into(), recipient_pubkey()) @@ -945,7 +1171,7 @@ mod tests { .amount_msats(MAX_VALUE_MSAT + 1) { Ok(_) => panic!("expected error"), - Err(e) => assert_eq!(e, SemanticError::InvalidAmount), + Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidAmount), } match OfferBuilder::new("foo".into(), recipient_pubkey()) @@ -958,7 +1184,7 @@ mod tests { .build() { Ok(_) => panic!("expected error"), - Err(e) => assert_eq!(e, SemanticError::InsufficientAmount), + Err(e) => assert_eq!(e, Bolt12SemanticError::InsufficientAmount), } match OfferBuilder::new("foo".into(), recipient_pubkey()) @@ -967,7 +1193,7 @@ mod tests { .build() { Ok(_) => panic!("expected error"), - Err(e) => assert_eq!(e, SemanticError::MissingAmount), + Err(e) => assert_eq!(e, Bolt12SemanticError::MissingAmount), } match OfferBuilder::new("foo".into(), recipient_pubkey()) @@ -979,7 +1205,7 @@ mod tests { .build() { Ok(_) => panic!("expected error"), - Err(e) => assert_eq!(e, SemanticError::InvalidAmount), + Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidAmount), } } @@ -1034,7 +1260,7 @@ mod tests { .quantity(2) { Ok(_) => panic!("expected error"), - Err(e) => assert_eq!(e, SemanticError::UnexpectedQuantity), + Err(e) => assert_eq!(e, Bolt12SemanticError::UnexpectedQuantity), } let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) @@ -1059,7 +1285,7 @@ mod tests { .quantity(11) { Ok(_) => panic!("expected error"), - Err(e) => assert_eq!(e, SemanticError::InvalidQuantity), + Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidQuantity), } let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) @@ -1083,7 +1309,7 @@ mod tests { .build() { Ok(_) => panic!("expected error"), - Err(e) => assert_eq!(e, SemanticError::MissingQuantity), + Err(e) => assert_eq!(e, Bolt12SemanticError::MissingQuantity), } match OfferBuilder::new("foo".into(), recipient_pubkey()) @@ -1094,7 +1320,7 @@ mod tests { .build() { Ok(_) => panic!("expected error"), - Err(e) => assert_eq!(e, SemanticError::MissingQuantity), + Err(e) => assert_eq!(e, Bolt12SemanticError::MissingQuantity), } } @@ -1161,7 +1387,7 @@ mod tests { .respond_with_no_std(payment_paths(), payment_hash(), now()) { Ok(_) => panic!("expected error"), - Err(e) => assert_eq!(e, SemanticError::UnknownRequiredFeatures), + Err(e) => assert_eq!(e, Bolt12SemanticError::UnknownRequiredFeatures), } } @@ -1212,7 +1438,7 @@ mod tests { match InvoiceRequest::try_from(buffer) { Ok(_) => panic!("expected error"), - Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnsupportedChain)), + Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::UnsupportedChain)), } } @@ -1257,7 +1483,7 @@ mod tests { match InvoiceRequest::try_from(buffer) { Ok(_) => panic!("expected error"), - Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingAmount)), + Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingAmount)), } let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) @@ -1273,7 +1499,7 @@ mod tests { match InvoiceRequest::try_from(buffer) { Ok(_) => panic!("expected error"), - Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InsufficientAmount)), + Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::InsufficientAmount)), } let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) @@ -1289,7 +1515,7 @@ mod tests { match InvoiceRequest::try_from(buffer) { Ok(_) => panic!("expected error"), Err(e) => { - assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnsupportedCurrency)); + assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::UnsupportedCurrency)); }, } @@ -1307,7 +1533,7 @@ mod tests { match InvoiceRequest::try_from(buffer) { Ok(_) => panic!("expected error"), - Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InvalidAmount)), + Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::InvalidAmount)), } } @@ -1347,7 +1573,7 @@ mod tests { match InvoiceRequest::try_from(buffer) { Ok(_) => panic!("expected error"), Err(e) => { - assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnexpectedQuantity)); + assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::UnexpectedQuantity)); }, } @@ -1383,7 +1609,7 @@ mod tests { match InvoiceRequest::try_from(buffer) { Ok(_) => panic!("expected error"), - Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InvalidQuantity)), + Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::InvalidQuantity)), } let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) @@ -1416,7 +1642,7 @@ mod tests { match InvoiceRequest::try_from(buffer) { Ok(_) => panic!("expected error"), - Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingQuantity)), + Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingQuantity)), } let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) @@ -1432,7 +1658,7 @@ mod tests { match InvoiceRequest::try_from(buffer) { Ok(_) => panic!("expected error"), - Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingQuantity)), + Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingQuantity)), } } @@ -1452,7 +1678,7 @@ mod tests { match InvoiceRequest::try_from(buffer) { Ok(_) => panic!("expected error"), Err(e) => { - assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingPayerMetadata)); + assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingPayerMetadata)); }, } } @@ -1472,7 +1698,7 @@ mod tests { match InvoiceRequest::try_from(buffer) { Ok(_) => panic!("expected error"), - Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingPayerId)), + Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingPayerId)), } } @@ -1492,7 +1718,7 @@ mod tests { match InvoiceRequest::try_from(buffer) { Ok(_) => panic!("expected error"), Err(e) => { - assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingSigningPubkey)); + assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingSigningPubkey)); }, } } @@ -1510,7 +1736,7 @@ mod tests { match InvoiceRequest::try_from(buffer) { Ok(_) => panic!("expected error"), - Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingSignature)), + Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingSignature)), } } @@ -1531,7 +1757,7 @@ mod tests { match InvoiceRequest::try_from(buffer) { Ok(_) => panic!("expected error"), Err(e) => { - assert_eq!(e, ParseError::InvalidSignature(secp256k1::Error::InvalidSignature)); + assert_eq!(e, Bolt12ParseError::InvalidSignature(secp256k1::Error::InvalidSignature)); }, } } @@ -1556,7 +1782,7 @@ mod tests { match InvoiceRequest::try_from(encoded_invoice_request) { Ok(_) => panic!("expected error"), - Err(e) => assert_eq!(e, ParseError::Decode(DecodeError::InvalidValue)), + Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::InvalidValue)), } } }