@@ -1032,6 +1032,11 @@ impl Bolt12Invoice {
10321032 InvoiceContents :: ForRefund { .. } => self . message_paths ( ) . is_empty ( ) ,
10331033 }
10341034 }
1035+
1036+ /// Returns the [`TaggedHash`] of the invoice that was signed.
1037+ pub fn tagged_hash ( & self ) -> & TaggedHash {
1038+ & self . tagged_hash
1039+ }
10351040}
10361041
10371042impl PartialEq for Bolt12Invoice {
@@ -1777,6 +1782,14 @@ mod tests {
17771782 EXPERIMENTAL_INVOICE_TYPES , INVOICE_TYPES , SIGNATURE_TAG ,
17781783 } ;
17791784
1785+ use crate :: ln:: channelmanager:: PaymentId ;
1786+ use crate :: ln:: inbound_payment:: ExpandedKey ;
1787+ use crate :: offers:: merkle;
1788+ use crate :: offers:: nonce:: Nonce ;
1789+ use crate :: offers:: offer:: OfferBuilder ;
1790+ use crate :: offers:: test_utils:: {
1791+ payment_hash, payment_paths, recipient_pubkey, recipient_sign, FixedEntropy ,
1792+ } ;
17801793 use bitcoin:: address:: Address ;
17811794 use bitcoin:: constants:: ChainHash ;
17821795 use bitcoin:: hashes:: Hash ;
@@ -1785,37 +1798,8 @@ mod tests {
17851798 use bitcoin:: script:: ScriptBuf ;
17861799 use bitcoin:: secp256k1:: { self , Keypair , Message , Secp256k1 , SecretKey , XOnlyPublicKey } ;
17871800 use bitcoin:: { CompressedPublicKey , WitnessProgram , WitnessVersion } ;
1788-
17891801 use core:: time:: Duration ;
17901802
1791- use crate :: blinded_path:: message:: BlindedMessagePath ;
1792- use crate :: blinded_path:: BlindedHop ;
1793- use crate :: ln:: channelmanager:: PaymentId ;
1794- use crate :: ln:: inbound_payment:: ExpandedKey ;
1795- use crate :: ln:: msgs:: DecodeError ;
1796- use crate :: offers:: invoice_request:: {
1797- ExperimentalInvoiceRequestTlvStreamRef , InvoiceRequestTlvStreamRef ,
1798- } ;
1799- use crate :: offers:: merkle:: { self , SignError , SignatureTlvStreamRef , TaggedHash , TlvStream } ;
1800- use crate :: offers:: nonce:: Nonce ;
1801- use crate :: offers:: offer:: {
1802- Amount , ExperimentalOfferTlvStreamRef , OfferTlvStreamRef , Quantity ,
1803- } ;
1804- use crate :: offers:: parse:: { Bolt12ParseError , Bolt12SemanticError } ;
1805- use crate :: offers:: payer:: PayerTlvStreamRef ;
1806- use crate :: offers:: test_utils:: * ;
1807- use crate :: prelude:: * ;
1808- use crate :: types:: features:: { Bolt12InvoiceFeatures , InvoiceRequestFeatures , OfferFeatures } ;
1809- use crate :: types:: string:: PrintableString ;
1810- use crate :: util:: ser:: { BigSize , Iterable , Writeable } ;
1811- #[ cfg( not( c_bindings) ) ]
1812- use { crate :: offers:: offer:: OfferBuilder , crate :: offers:: refund:: RefundBuilder } ;
1813- #[ cfg( c_bindings) ]
1814- use {
1815- crate :: offers:: offer:: OfferWithExplicitMetadataBuilder as OfferBuilder ,
1816- crate :: offers:: refund:: RefundMaybeWithDerivedMetadataBuilder as RefundBuilder ,
1817- } ;
1818-
18191803 trait ToBytes {
18201804 fn to_bytes ( & self ) -> Vec < u8 > ;
18211805 }
@@ -3560,4 +3544,86 @@ mod tests {
35603544 ) ,
35613545 }
35623546 }
3547+
3548+ #[ test]
3549+ fn invoice_offer_id_matches_offer_id ( ) {
3550+ let expanded_key = ExpandedKey :: new ( [ 42 ; 32 ] ) ;
3551+ let entropy = FixedEntropy { } ;
3552+ let nonce = Nonce :: from_entropy_source ( & entropy) ;
3553+ let secp_ctx = Secp256k1 :: new ( ) ;
3554+ let payment_id = PaymentId ( [ 1 ; 32 ] ) ;
3555+
3556+ let offer = OfferBuilder :: new ( recipient_pubkey ( ) ) . amount_msats ( 1000 ) . build ( ) . unwrap ( ) ;
3557+
3558+ let offer_id = offer. id ( ) ;
3559+
3560+ let invoice_request = offer
3561+ . request_invoice ( & expanded_key, nonce, & secp_ctx, payment_id)
3562+ . unwrap ( )
3563+ . build_and_sign ( )
3564+ . unwrap ( ) ;
3565+
3566+ let invoice = invoice_request
3567+ . respond_with_no_std ( payment_paths ( ) , payment_hash ( ) , now ( ) )
3568+ . unwrap ( )
3569+ . build ( )
3570+ . unwrap ( )
3571+ . sign ( recipient_sign)
3572+ . unwrap ( ) ;
3573+
3574+ assert_eq ! ( invoice. offer_id( ) , Some ( offer_id) ) ;
3575+ }
3576+
3577+ #[ test]
3578+ fn refund_invoice_has_no_offer_id ( ) {
3579+ let refund =
3580+ RefundBuilder :: new ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) , 1000 ) . unwrap ( ) . build ( ) . unwrap ( ) ;
3581+
3582+ let invoice = refund
3583+ . respond_with_no_std ( payment_paths ( ) , payment_hash ( ) , recipient_pubkey ( ) , now ( ) )
3584+ . unwrap ( )
3585+ . build ( )
3586+ . unwrap ( )
3587+ . sign ( recipient_sign)
3588+ . unwrap ( ) ;
3589+
3590+ assert_eq ! ( invoice. offer_id( ) , None ) ;
3591+ }
3592+
3593+ #[ test]
3594+ fn verifies_invoice_signature_with_tagged_hash ( ) {
3595+ let secp_ctx = Secp256k1 :: new ( ) ;
3596+ let expanded_key = ExpandedKey :: new ( [ 42 ; 32 ] ) ;
3597+ let entropy = FixedEntropy { } ;
3598+ let nonce = Nonce :: from_entropy_source ( & entropy) ;
3599+ let node_id = recipient_pubkey ( ) ;
3600+ let payment_paths = payment_paths ( ) ;
3601+ let now = Duration :: from_secs ( 123456 ) ;
3602+ let payment_id = PaymentId ( [ 1 ; 32 ] ) ;
3603+
3604+ let offer = OfferBuilder :: new ( node_id)
3605+ . amount_msats ( 1000 )
3606+ . path ( crate :: offers:: test_utils:: blinded_path ( ) )
3607+ . build ( )
3608+ . unwrap ( ) ;
3609+
3610+ let invoice_request = offer
3611+ . request_invoice ( & expanded_key, nonce, & secp_ctx, payment_id)
3612+ . unwrap ( )
3613+ . build_and_sign ( )
3614+ . unwrap ( ) ;
3615+
3616+ let invoice = invoice_request
3617+ . respond_with_no_std ( payment_paths, payment_hash ( ) , now)
3618+ . unwrap ( )
3619+ . build ( )
3620+ . unwrap ( )
3621+ . sign ( recipient_sign)
3622+ . unwrap ( ) ;
3623+
3624+ let issuer_sign_pubkey = offer. issuer_signing_pubkey ( ) . unwrap ( ) ;
3625+ let tagged_hash = invoice. tagged_hash ( ) ;
3626+ let signature = invoice. signature ( ) ;
3627+ assert ! ( merkle:: verify_signature( & signature, tagged_hash, issuer_sign_pubkey) . is_ok( ) ) ;
3628+ }
35633629}
0 commit comments