@@ -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,14 @@ impl UnsignedBolt12Invoice {
665665 pub fn tagged_hash ( & self ) -> & TaggedHash {
666666 & self . tagged_hash
667667 }
668+
669+ /// Computes the [`OfferId`] if this invoice corresponds to an [`Offer`].
670+ fn compute_offer_id ( & self ) -> Option < OfferId > {
671+ match & self . contents {
672+ InvoiceContents :: ForOffer { .. } => Some ( OfferId :: from_invoice_bytes ( & self . bytes ) ) ,
673+ InvoiceContents :: ForRefund { .. } => None ,
674+ }
675+ }
668676}
669677
670678macro_rules! unsigned_invoice_sign_method { ( $self: ident, $self_type: ty $( , $self_mut: tt) ?) => {
@@ -686,6 +694,9 @@ macro_rules! unsigned_invoice_sign_method { ($self: ident, $self_type: ty $(, $s
686694 // Append the experimental bytes after the signature.
687695 $self. bytes. extend_from_slice( & $self. experimental_bytes) ;
688696
697+ // Compute offer_id before moving fields
698+ let offer_id = $self. compute_offer_id( ) ;
699+
689700 Ok ( Bolt12Invoice {
690701 #[ cfg( not( c_bindings) ) ]
691702 bytes: $self. bytes,
@@ -700,6 +711,7 @@ macro_rules! unsigned_invoice_sign_method { ($self: ident, $self_type: ty $(, $s
700711 tagged_hash: $self. tagged_hash,
701712 #[ cfg( c_bindings) ]
702713 tagged_hash: $self. tagged_hash. clone( ) ,
714+ offer_id,
703715 } )
704716 }
705717} }
@@ -734,6 +746,7 @@ pub struct Bolt12Invoice {
734746 contents : InvoiceContents ,
735747 signature : Signature ,
736748 tagged_hash : TaggedHash ,
749+ offer_id : Option < OfferId > ,
737750}
738751
739752/// The contents of an [`Bolt12Invoice`] for responding to either an [`Offer`] or a [`Refund`].
@@ -967,6 +980,11 @@ impl Bolt12Invoice {
967980 self . tagged_hash . as_digest ( ) . as_ref ( ) . clone ( )
968981 }
969982
983+ /// Returns the [`OfferId`] if this invoice corresponds to an [`Offer`].
984+ pub fn offer_id ( & self ) -> Option < OfferId > {
985+ self . offer_id
986+ }
987+
970988 /// Verifies that the invoice was for a request or refund created using the given key by
971989 /// checking the payer metadata from the invoice request.
972990 ///
@@ -1622,7 +1640,8 @@ impl TryFrom<ParsedMessage<FullInvoiceTlvStream>> for Bolt12Invoice {
16221640 let pubkey = contents. fields ( ) . signing_pubkey ;
16231641 merkle:: verify_signature ( & signature, & tagged_hash, pubkey) ?;
16241642
1625- Ok ( Bolt12Invoice { bytes, contents, signature, tagged_hash } )
1643+ let offer_id = OfferId :: from_invoice_bytes ( & bytes) ;
1644+ Ok ( Bolt12Invoice { bytes, contents, signature, tagged_hash, offer_id : Some ( offer_id) } )
16261645 }
16271646}
16281647
@@ -3556,4 +3575,54 @@ mod tests {
35563575 ) ,
35573576 }
35583577 }
3578+
3579+ #[ test]
3580+ fn invoice_offer_id_matches_offer_id ( ) {
3581+ let expanded_key = ExpandedKey :: new ( [ 42 ; 32 ] ) ;
3582+ let entropy = FixedEntropy { } ;
3583+ let nonce = Nonce :: from_entropy_source ( & entropy) ;
3584+ let secp_ctx = Secp256k1 :: new ( ) ;
3585+ let payment_id = PaymentId ( [ 1 ; 32 ] ) ;
3586+
3587+ let offer = OfferBuilder :: new ( recipient_pubkey ( ) )
3588+ . amount_msats ( 1000 )
3589+ . build ( )
3590+ . unwrap ( ) ;
3591+
3592+ let offer_id = offer. id ( ) ;
3593+
3594+ let invoice_request = offer
3595+ . request_invoice ( & expanded_key, nonce, & secp_ctx, payment_id)
3596+ . unwrap ( )
3597+ . build_and_sign ( )
3598+ . unwrap ( ) ;
3599+
3600+ let invoice = invoice_request
3601+ . respond_with_no_std ( payment_paths ( ) , payment_hash ( ) , now ( ) )
3602+ . unwrap ( )
3603+ . build ( )
3604+ . unwrap ( )
3605+ . sign ( recipient_sign)
3606+ . unwrap ( ) ;
3607+
3608+ assert_eq ! ( invoice. offer_id( ) , Some ( offer_id) ) ;
3609+ }
3610+
3611+ #[ test]
3612+ fn refund_invoice_has_no_offer_id ( ) {
3613+ let refund = RefundBuilder :: new ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) , 1000 )
3614+ . unwrap ( )
3615+ . build ( )
3616+ . unwrap ( ) ;
3617+
3618+ let invoice = refund
3619+ . respond_with_no_std ( payment_paths ( ) , payment_hash ( ) , recipient_pubkey ( ) , now ( ) )
3620+ . unwrap ( )
3621+ . build ( )
3622+ . unwrap ( )
3623+ . sign ( recipient_sign)
3624+ . unwrap ( ) ;
3625+
3626+ assert_eq ! ( invoice. offer_id( ) , None ) ;
3627+ }
35593628}
0 commit comments