@@ -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 } ;
@@ -686,6 +686,13 @@ macro_rules! unsigned_invoice_sign_method { ($self: ident, $self_type: ty $(, $s
686
686
// Append the experimental bytes after the signature.
687
687
$self. bytes. extend_from_slice( & $self. experimental_bytes) ;
688
688
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
+
689
696
Ok ( Bolt12Invoice {
690
697
#[ cfg( not( c_bindings) ) ]
691
698
bytes: $self. bytes,
@@ -700,6 +707,7 @@ macro_rules! unsigned_invoice_sign_method { ($self: ident, $self_type: ty $(, $s
700
707
tagged_hash: $self. tagged_hash,
701
708
#[ cfg( c_bindings) ]
702
709
tagged_hash: $self. tagged_hash. clone( ) ,
710
+ offer_id,
703
711
} )
704
712
}
705
713
} }
@@ -734,6 +742,7 @@ pub struct Bolt12Invoice {
734
742
contents : InvoiceContents ,
735
743
signature : Signature ,
736
744
tagged_hash : TaggedHash ,
745
+ offer_id : Option < OfferId > ,
737
746
}
738
747
739
748
/// The contents of an [`Bolt12Invoice`] for responding to either an [`Offer`] or a [`Refund`].
@@ -967,6 +976,13 @@ impl Bolt12Invoice {
967
976
self . tagged_hash . as_digest ( ) . as_ref ( ) . clone ( )
968
977
}
969
978
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
+
970
986
/// Verifies that the invoice was for a request or refund created using the given key by
971
987
/// checking the payer metadata from the invoice request.
972
988
///
@@ -1032,6 +1048,11 @@ impl Bolt12Invoice {
1032
1048
InvoiceContents :: ForRefund { .. } => self . message_paths ( ) . is_empty ( ) ,
1033
1049
}
1034
1050
}
1051
+
1052
+ /// Returns the [`TaggedHash`] of the invoice that was signed.
1053
+ pub fn tagged_hash ( & self ) -> & TaggedHash {
1054
+ & self . tagged_hash
1055
+ }
1035
1056
}
1036
1057
1037
1058
impl PartialEq for Bolt12Invoice {
@@ -1626,7 +1647,11 @@ impl TryFrom<ParsedMessage<FullInvoiceTlvStream>> for Bolt12Invoice {
1626
1647
let pubkey = contents. fields ( ) . signing_pubkey ;
1627
1648
merkle:: verify_signature ( & signature, & tagged_hash, pubkey) ?;
1628
1649
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 } )
1630
1655
}
1631
1656
}
1632
1657
@@ -1785,7 +1810,6 @@ mod tests {
1785
1810
use bitcoin:: script:: ScriptBuf ;
1786
1811
use bitcoin:: secp256k1:: { self , Keypair , Message , Secp256k1 , SecretKey , XOnlyPublicKey } ;
1787
1812
use bitcoin:: { CompressedPublicKey , WitnessProgram , WitnessVersion } ;
1788
-
1789
1813
use core:: time:: Duration ;
1790
1814
1791
1815
use crate :: blinded_path:: message:: BlindedMessagePath ;
@@ -3560,4 +3584,85 @@ mod tests {
3560
3584
) ,
3561
3585
}
3562
3586
}
3587
+
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
+
3633
+ #[ test]
3634
+ fn verifies_invoice_signature_with_tagged_hash ( ) {
3635
+ let secp_ctx = Secp256k1 :: new ( ) ;
3636
+ let expanded_key = ExpandedKey :: new ( [ 42 ; 32 ] ) ;
3637
+ let entropy = FixedEntropy { } ;
3638
+ let nonce = Nonce :: from_entropy_source ( & entropy) ;
3639
+ let node_id = recipient_pubkey ( ) ;
3640
+ let payment_paths = payment_paths ( ) ;
3641
+ let now = Duration :: from_secs ( 123456 ) ;
3642
+ let payment_id = PaymentId ( [ 1 ; 32 ] ) ;
3643
+
3644
+ let offer = OfferBuilder :: new ( node_id)
3645
+ . amount_msats ( 1000 )
3646
+ . build ( )
3647
+ . unwrap ( ) ;
3648
+
3649
+ let invoice_request = offer
3650
+ . request_invoice ( & expanded_key, nonce, & secp_ctx, payment_id)
3651
+ . unwrap ( )
3652
+ . build_and_sign ( )
3653
+ . unwrap ( ) ;
3654
+
3655
+ let invoice = invoice_request
3656
+ . respond_with_no_std ( payment_paths, payment_hash ( ) , now)
3657
+ . unwrap ( )
3658
+ . build ( )
3659
+ . unwrap ( )
3660
+ . sign ( recipient_sign)
3661
+ . unwrap ( ) ;
3662
+
3663
+ let issuer_sign_pubkey = offer. issuer_signing_pubkey ( ) . unwrap ( ) ;
3664
+ let tagged_hash = invoice. tagged_hash ( ) ;
3665
+ let signature = invoice. signature ( ) ;
3666
+ assert ! ( merkle:: verify_signature( & signature, tagged_hash, issuer_sign_pubkey) . is_ok( ) ) ;
3667
+ }
3563
3668
}
0 commit comments