@@ -135,7 +135,7 @@ use crate::offers::merkle::{
135
135
} ;
136
136
use crate :: offers:: nonce:: Nonce ;
137
137
use crate :: offers:: offer:: {
138
- Amount , ExperimentalOfferTlvStream , ExperimentalOfferTlvStreamRef , OfferTlvStream ,
138
+ Amount , ExperimentalOfferTlvStream , ExperimentalOfferTlvStreamRef , OfferId , OfferTlvStream ,
139
139
OfferTlvStreamRef , Quantity , EXPERIMENTAL_OFFER_TYPES , OFFER_TYPES ,
140
140
} ;
141
141
use crate :: offers:: parse:: { Bolt12ParseError , Bolt12SemanticError , ParsedMessage } ;
@@ -665,6 +665,14 @@ impl UnsignedBolt12Invoice {
665
665
pub fn tagged_hash ( & self ) -> & TaggedHash {
666
666
& self . tagged_hash
667
667
}
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
+ }
668
676
}
669
677
670
678
macro_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
686
694
// Append the experimental bytes after the signature.
687
695
$self. bytes. extend_from_slice( & $self. experimental_bytes) ;
688
696
697
+ // Compute offer_id before moving fields
698
+ let offer_id = $self. compute_offer_id( ) ;
699
+
689
700
Ok ( Bolt12Invoice {
690
701
#[ cfg( not( c_bindings) ) ]
691
702
bytes: $self. bytes,
@@ -700,6 +711,7 @@ macro_rules! unsigned_invoice_sign_method { ($self: ident, $self_type: ty $(, $s
700
711
tagged_hash: $self. tagged_hash,
701
712
#[ cfg( c_bindings) ]
702
713
tagged_hash: $self. tagged_hash. clone( ) ,
714
+ offer_id,
703
715
} )
704
716
}
705
717
} }
@@ -734,6 +746,7 @@ pub struct Bolt12Invoice {
734
746
contents : InvoiceContents ,
735
747
signature : Signature ,
736
748
tagged_hash : TaggedHash ,
749
+ offer_id : Option < OfferId > ,
737
750
}
738
751
739
752
/// The contents of an [`Bolt12Invoice`] for responding to either an [`Offer`] or a [`Refund`].
@@ -967,6 +980,11 @@ impl Bolt12Invoice {
967
980
self . tagged_hash . as_digest ( ) . as_ref ( ) . clone ( )
968
981
}
969
982
983
+ /// Returns the [`OfferId`] if this invoice corresponds to an [`crate::offers::offer::Offer`].
984
+ pub fn offer_id ( & self ) -> Option < OfferId > {
985
+ self . offer_id
986
+ }
987
+
970
988
/// Verifies that the invoice was for a request or refund created using the given key by
971
989
/// checking the payer metadata from the invoice request.
972
990
///
@@ -1622,7 +1640,11 @@ impl TryFrom<ParsedMessage<FullInvoiceTlvStream>> for Bolt12Invoice {
1622
1640
let pubkey = contents. fields ( ) . signing_pubkey ;
1623
1641
merkle:: verify_signature ( & signature, & tagged_hash, pubkey) ?;
1624
1642
1625
- Ok ( Bolt12Invoice { bytes, contents, signature, tagged_hash } )
1643
+ let offer_id = match & contents {
1644
+ InvoiceContents :: ForOffer { .. } => Some ( OfferId :: from_invoice_bytes ( & bytes) ) ,
1645
+ InvoiceContents :: ForRefund { .. } => None ,
1646
+ } ;
1647
+ Ok ( Bolt12Invoice { bytes, contents, signature, tagged_hash, offer_id } )
1626
1648
}
1627
1649
}
1628
1650
@@ -3556,4 +3578,49 @@ mod tests {
3556
3578
) ,
3557
3579
}
3558
3580
}
3581
+
3582
+ #[ test]
3583
+ fn invoice_offer_id_matches_offer_id ( ) {
3584
+ let expanded_key = ExpandedKey :: new ( [ 42 ; 32 ] ) ;
3585
+ let entropy = FixedEntropy { } ;
3586
+ let nonce = Nonce :: from_entropy_source ( & entropy) ;
3587
+ let secp_ctx = Secp256k1 :: new ( ) ;
3588
+ let payment_id = PaymentId ( [ 1 ; 32 ] ) ;
3589
+
3590
+ let offer = OfferBuilder :: new ( recipient_pubkey ( ) ) . amount_msats ( 1000 ) . build ( ) . 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 =
3614
+ RefundBuilder :: new ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) , 1000 ) . unwrap ( ) . build ( ) . unwrap ( ) ;
3615
+
3616
+ let invoice = refund
3617
+ . respond_with_no_std ( payment_paths ( ) , payment_hash ( ) , recipient_pubkey ( ) , now ( ) )
3618
+ . unwrap ( )
3619
+ . build ( )
3620
+ . unwrap ( )
3621
+ . sign ( recipient_sign)
3622
+ . unwrap ( ) ;
3623
+
3624
+ assert_eq ! ( invoice. offer_id( ) , None ) ;
3625
+ }
3559
3626
}
0 commit comments