@@ -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 } ;
@@ -686,6 +686,13 @@ macro_rules! unsigned_invoice_sign_method { ($self: ident, $self_type: ty $(, $s
686686 // Append the experimental bytes after the signature.
687687 $self. bytes. extend_from_slice( & $self. experimental_bytes) ;
688688
689+ let offer_id = match & $self. contents {
690+ InvoiceContents :: ForOffer { .. } => {
691+ Some ( OfferId :: from_valid_bolt12_tlv_stream( & $self. bytes) )
692+ } ,
693+ InvoiceContents :: ForRefund { .. } => None ,
694+ } ;
695+
689696 Ok ( Bolt12Invoice {
690697 #[ cfg( not( c_bindings) ) ]
691698 bytes: $self. bytes,
@@ -700,6 +707,7 @@ macro_rules! unsigned_invoice_sign_method { ($self: ident, $self_type: ty $(, $s
700707 tagged_hash: $self. tagged_hash,
701708 #[ cfg( c_bindings) ]
702709 tagged_hash: $self. tagged_hash. clone( ) ,
710+ offer_id,
703711 } )
704712 }
705713} }
@@ -734,6 +742,7 @@ pub struct Bolt12Invoice {
734742 contents : InvoiceContents ,
735743 signature : Signature ,
736744 tagged_hash : TaggedHash ,
745+ offer_id : Option < OfferId > ,
737746}
738747
739748/// The contents of an [`Bolt12Invoice`] for responding to either an [`Offer`] or a [`Refund`].
@@ -967,6 +976,13 @@ impl Bolt12Invoice {
967976 self . tagged_hash . as_digest ( ) . as_ref ( ) . clone ( )
968977 }
969978
979+ /// Returns the [`OfferId`] if this invoice corresponds to an [`Offer`].
980+ ///
981+ /// [`Offer`]: crate::offers::offer::Offer
982+ pub fn offer_id ( & self ) -> Option < OfferId > {
983+ self . offer_id
984+ }
985+
970986 /// Verifies that the invoice was for a request or refund created using the given key by
971987 /// checking the payer metadata from the invoice request.
972988 ///
@@ -1032,6 +1048,11 @@ impl Bolt12Invoice {
10321048 InvoiceContents :: ForRefund { .. } => self . message_paths ( ) . is_empty ( ) ,
10331049 }
10341050 }
1051+
1052+ /// Returns the [`TaggedHash`] of the invoice that was signed.
1053+ pub fn tagged_hash ( & self ) -> & TaggedHash {
1054+ & self . tagged_hash
1055+ }
10351056}
10361057
10371058impl PartialEq for Bolt12Invoice {
@@ -1626,7 +1647,11 @@ impl TryFrom<ParsedMessage<FullInvoiceTlvStream>> for Bolt12Invoice {
16261647 let pubkey = contents. fields ( ) . signing_pubkey ;
16271648 merkle:: verify_signature ( & signature, & tagged_hash, pubkey) ?;
16281649
1629- Ok ( Bolt12Invoice { bytes, contents, signature, tagged_hash } )
1650+ let offer_id = match & contents {
1651+ InvoiceContents :: ForOffer { .. } => Some ( OfferId :: from_valid_bolt12_tlv_stream ( & bytes) ) ,
1652+ InvoiceContents :: ForRefund { .. } => None ,
1653+ } ;
1654+ Ok ( Bolt12Invoice { bytes, contents, signature, tagged_hash, offer_id } )
16301655 }
16311656}
16321657
@@ -1785,7 +1810,6 @@ mod tests {
17851810 use bitcoin:: script:: ScriptBuf ;
17861811 use bitcoin:: secp256k1:: { self , Keypair , Message , Secp256k1 , SecretKey , XOnlyPublicKey } ;
17871812 use bitcoin:: { CompressedPublicKey , WitnessProgram , WitnessVersion } ;
1788-
17891813 use core:: time:: Duration ;
17901814
17911815 use crate :: blinded_path:: message:: BlindedMessagePath ;
@@ -3561,6 +3585,51 @@ mod tests {
35613585 }
35623586 }
35633587
3588+ #[ test]
3589+ fn invoice_offer_id_matches_offer_id ( ) {
3590+ let expanded_key = ExpandedKey :: new ( [ 42 ; 32 ] ) ;
3591+ let entropy = FixedEntropy { } ;
3592+ let nonce = Nonce :: from_entropy_source ( & entropy) ;
3593+ let secp_ctx = Secp256k1 :: new ( ) ;
3594+ let payment_id = PaymentId ( [ 1 ; 32 ] ) ;
3595+
3596+ let offer = OfferBuilder :: new ( recipient_pubkey ( ) ) . amount_msats ( 1000 ) . build ( ) . unwrap ( ) ;
3597+
3598+ let offer_id = offer. id ( ) ;
3599+
3600+ let invoice_request = offer
3601+ . request_invoice ( & expanded_key, nonce, & secp_ctx, payment_id)
3602+ . unwrap ( )
3603+ . build_and_sign ( )
3604+ . unwrap ( ) ;
3605+
3606+ let invoice = invoice_request
3607+ . respond_with_no_std ( payment_paths ( ) , payment_hash ( ) , now ( ) )
3608+ . unwrap ( )
3609+ . build ( )
3610+ . unwrap ( )
3611+ . sign ( recipient_sign)
3612+ . unwrap ( ) ;
3613+
3614+ assert_eq ! ( invoice. offer_id( ) , Some ( offer_id) ) ;
3615+ }
3616+
3617+ #[ test]
3618+ fn refund_invoice_has_no_offer_id ( ) {
3619+ let refund =
3620+ RefundBuilder :: new ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) , 1000 ) . unwrap ( ) . build ( ) . unwrap ( ) ;
3621+
3622+ let invoice = refund
3623+ . respond_with_no_std ( payment_paths ( ) , payment_hash ( ) , recipient_pubkey ( ) , now ( ) )
3624+ . unwrap ( )
3625+ . build ( )
3626+ . unwrap ( )
3627+ . sign ( recipient_sign)
3628+ . unwrap ( ) ;
3629+
3630+ assert_eq ! ( invoice. offer_id( ) , None ) ;
3631+ }
3632+
35643633 #[ test]
35653634 fn verifies_invoice_signature_with_tagged_hash ( ) {
35663635 let secp_ctx = Secp256k1 :: new ( ) ;
0 commit comments