@@ -135,7 +135,7 @@ use crate::offers::merkle::{
135135} ; 
136136use  crate :: offers:: nonce:: Nonce ; 
137137use  crate :: offers:: offer:: { 
138- 	Amount ,  ExperimentalOfferTlvStream ,  ExperimentalOfferTlvStreamRef ,  OfferTlvStream , 
138+ 	Amount ,  ExperimentalOfferTlvStream ,  ExperimentalOfferTlvStreamRef ,  OfferId ,   OfferTlvStream , 
139139	OfferTlvStreamRef ,  Quantity ,  EXPERIMENTAL_OFFER_TYPES ,  OFFER_TYPES , 
140140} ; 
141141use  crate :: offers:: parse:: { Bolt12ParseError ,  Bolt12SemanticError ,  ParsedMessage } ; 
@@ -665,6 +665,21 @@ impl UnsignedBolt12Invoice {
665665 	pub  fn  tagged_hash ( & self )  -> & TaggedHash  { 
666666		& self . tagged_hash 
667667	} 
668+ 
669+ 	/// Computes the offer ID if this invoice corresponds to an offer. 
670+  	fn  compute_offer_id ( & self )  -> Option < OfferId >  { 
671+ 		match  & self . contents  { 
672+ 			InvoiceContents :: ForOffer  {  .. }  => { 
673+ 				// Extract offer TLV records from the invoice bytes 
674+ 				let  offer_tlv_stream = TlvStream :: new ( & self . bytes ) . range ( OFFER_TYPES ) ; 
675+ 				let  experimental_offer_tlv_stream = TlvStream :: new ( & self . experimental_bytes ) . range ( EXPERIMENTAL_OFFER_TYPES ) ; 
676+ 				let  combined_tlv_stream = offer_tlv_stream. chain ( experimental_offer_tlv_stream) ; 
677+ 				let  tagged_hash = TaggedHash :: from_tlv_stream ( "LDK Offer ID" ,  combined_tlv_stream) ; 
678+ 				Some ( OfferId ( tagged_hash. to_bytes ( ) ) ) 
679+ 			} , 
680+ 			InvoiceContents :: ForRefund  {  .. }  => None , 
681+ 		} 
682+ 	} 
668683} 
669684
670685macro_rules!  unsigned_invoice_sign_method {  ( $self:  ident,  $self_type:  ty $( ,  $self_mut:  tt) ?)  => { 
@@ -686,6 +701,9 @@ macro_rules! unsigned_invoice_sign_method { ($self: ident, $self_type: ty $(, $s
686701		// Append the experimental bytes after the signature. 
687702		$self. bytes. extend_from_slice( & $self. experimental_bytes) ; 
688703
704+ 		// Compute offer_id before moving fields 
705+ 		let  offer_id = $self. compute_offer_id( ) ; 
706+ 
689707		Ok ( Bolt12Invoice  { 
690708			#[ cfg( not( c_bindings) ) ] 
691709			bytes:  $self. bytes, 
@@ -700,6 +718,7 @@ macro_rules! unsigned_invoice_sign_method { ($self: ident, $self_type: ty $(, $s
700718			tagged_hash:  $self. tagged_hash, 
701719			#[ cfg( c_bindings) ] 
702720			tagged_hash:  $self. tagged_hash. clone( ) , 
721+ 			offer_id, 
703722		} ) 
704723	} 
705724}  } 
@@ -734,6 +753,7 @@ pub struct Bolt12Invoice {
734753	contents :  InvoiceContents , 
735754	signature :  Signature , 
736755	tagged_hash :  TaggedHash , 
756+ 	offer_id :  Option < OfferId > , 
737757} 
738758
739759/// The contents of an [`Bolt12Invoice`] for responding to either an [`Offer`] or a [`Refund`]. 
@@ -967,6 +987,11 @@ impl Bolt12Invoice {
967987		self . tagged_hash . as_digest ( ) . as_ref ( ) . clone ( ) 
968988	} 
969989
990+ 	/// Returns the offer ID if this invoice corresponds to an offer. 
991+  	pub  fn  offer_id ( & self )  -> Option < OfferId >  { 
992+ 		self . offer_id 
993+ 	} 
994+ 
970995	/// Verifies that the invoice was for a request or refund created using the given key by 
971996 	/// checking the payer metadata from the invoice request. 
972997 	/// 
@@ -1622,7 +1647,12 @@ impl TryFrom<ParsedMessage<FullInvoiceTlvStream>> for Bolt12Invoice {
16221647		let  pubkey = contents. fields ( ) . signing_pubkey ; 
16231648		merkle:: verify_signature ( & signature,  & tagged_hash,  pubkey) ?; 
16241649
1625- 		Ok ( Bolt12Invoice  {  bytes,  contents,  signature,  tagged_hash } ) 
1650+ 		let  offer_tlv_stream = TlvStream :: new ( & bytes) . range ( OFFER_TYPES ) ; 
1651+ 		let  experimental_offer_tlv_stream = TlvStream :: new ( & bytes) . range ( EXPERIMENTAL_OFFER_TYPES ) ; 
1652+ 		let  combined_tlv_stream = offer_tlv_stream. chain ( experimental_offer_tlv_stream) ; 
1653+ 		let  offer_tagged_hash = TaggedHash :: from_tlv_stream ( "LDK Offer ID" ,  combined_tlv_stream) ; 
1654+ 		let  offer_id = OfferId :: from_tagged_hash ( & offer_tagged_hash) ; 
1655+ 		Ok ( Bolt12Invoice  {  bytes,  contents,  signature,  tagged_hash,  offer_id :  Some ( offer_id)  } ) 
16261656	} 
16271657} 
16281658
@@ -3556,4 +3586,62 @@ mod tests {
35563586			) , 
35573587		} 
35583588	} 
3589+ 
3590+ 	#[ test]  
3591+ 	fn  invoice_offer_id_matches_offer_id ( )  { 
3592+ 		let  expanded_key = ExpandedKey :: new ( [ 42 ;  32 ] ) ; 
3593+ 		let  entropy = FixedEntropy  { } ; 
3594+ 		let  nonce = Nonce :: from_entropy_source ( & entropy) ; 
3595+ 		let  secp_ctx = Secp256k1 :: new ( ) ; 
3596+ 		let  payment_id = PaymentId ( [ 1 ;  32 ] ) ; 
3597+ 
3598+ 		// Create an offer 
3599+ 		let  offer = OfferBuilder :: new ( recipient_pubkey ( ) ) 
3600+ 			. amount_msats ( 1000 ) 
3601+ 			. build ( ) 
3602+ 			. unwrap ( ) ; 
3603+ 
3604+ 		// Get the offer ID 
3605+ 		let  offer_id = offer. id ( ) ; 
3606+ 
3607+ 		// Create an invoice request from the offer 
3608+ 		let  invoice_request = offer
3609+ 			. request_invoice ( & expanded_key,  nonce,  & secp_ctx,  payment_id) 
3610+ 			. unwrap ( ) 
3611+ 			. build_and_sign ( ) 
3612+ 			. unwrap ( ) ; 
3613+ 
3614+ 		// Create an invoice from the invoice request 
3615+ 		let  invoice = invoice_request
3616+ 			. respond_with_no_std ( payment_paths ( ) ,  payment_hash ( ) ,  now ( ) ) 
3617+ 			. unwrap ( ) 
3618+ 			. build ( ) 
3619+ 			. unwrap ( ) 
3620+ 			. sign ( recipient_sign) 
3621+ 			. unwrap ( ) ; 
3622+ 
3623+ 		// Verify that the invoice's offer_id matches the offer's id 
3624+ 		assert_eq ! ( invoice. offer_id( ) ,  Some ( offer_id) ) ; 
3625+ 	} 
3626+ 
3627+ 	#[ test]  
3628+ 	fn  refund_invoice_has_no_offer_id ( )  { 
3629+ 		// Create a refund 
3630+ 		let  refund = RefundBuilder :: new ( vec ! [ 1 ;  32 ] ,  payer_pubkey ( ) ,  1000 ) 
3631+ 			. unwrap ( ) 
3632+ 			. build ( ) 
3633+ 			. unwrap ( ) ; 
3634+ 
3635+ 		// Create an invoice from the refund 
3636+ 		let  invoice = refund
3637+ 			. respond_with_no_std ( payment_paths ( ) ,  payment_hash ( ) ,  recipient_pubkey ( ) ,  now ( ) ) 
3638+ 			. unwrap ( ) 
3639+ 			. build ( ) 
3640+ 			. unwrap ( ) 
3641+ 			. sign ( recipient_sign) 
3642+ 			. unwrap ( ) ; 
3643+ 
3644+ 		// Verify that the refund invoice has no offer_id 
3645+ 		assert_eq ! ( invoice. offer_id( ) ,  None ) ; 
3646+ 	} 
35593647} 
0 commit comments