@@ -131,7 +131,8 @@ use crate::offers::invoice_request::{
131131 IV_BYTES as INVOICE_REQUEST_IV_BYTES ,
132132} ;
133133use crate :: offers:: merkle:: {
134- self , SignError , SignFn , SignatureTlvStream , SignatureTlvStreamRef , TaggedHash , TlvStream ,
134+ self , SignError , SignFn , SignatureTlvStream , SignatureTlvStreamRef , TaggedHash , TlvRecord ,
135+ TlvStream ,
135136} ;
136137use crate :: offers:: nonce:: Nonce ;
137138use crate :: offers:: offer:: {
@@ -140,13 +141,15 @@ use crate::offers::offer::{
140141} ;
141142use crate :: offers:: parse:: { Bolt12ParseError , Bolt12SemanticError , ParsedMessage } ;
142143use crate :: offers:: payer:: { PayerTlvStream , PayerTlvStreamRef , PAYER_METADATA_TYPE } ;
144+ use crate :: offers:: payer_proof:: { PayerProofBuilder , PayerProofError } ;
143145use crate :: offers:: refund:: {
144146 Refund , RefundContents , IV_BYTES_WITHOUT_METADATA as REFUND_IV_BYTES_WITHOUT_METADATA ,
145147 IV_BYTES_WITH_METADATA as REFUND_IV_BYTES_WITH_METADATA ,
146148} ;
147149use crate :: offers:: signer:: { self , Metadata } ;
148150use crate :: types:: features:: { Bolt12InvoiceFeatures , InvoiceRequestFeatures , OfferFeatures } ;
149151use crate :: types:: payment:: PaymentHash ;
152+ use crate :: types:: payment:: PaymentPreimage ;
150153use crate :: types:: string:: PrintableString ;
151154use crate :: util:: ser:: {
152155 CursorReadable , HighZeroBytesDroppedBigSize , Iterable , LengthLimitedRead , LengthReadable ,
@@ -1032,6 +1035,42 @@ impl Bolt12Invoice {
10321035 )
10331036 }
10341037
1038+ /// Creates a [`PayerProofBuilder`] for this invoice using the given payment preimage.
1039+ ///
1040+ /// Returns an error if the preimage doesn't match the invoice's payment hash.
1041+ ///
1042+ /// [`PayerProofBuilder`]: crate::offers::payer_proof::PayerProofBuilder
1043+ pub fn payer_proof_builder (
1044+ & self , preimage : PaymentPreimage ,
1045+ ) -> Result < PayerProofBuilder < ' _ > , PayerProofError > {
1046+ PayerProofBuilder :: new ( self , preimage)
1047+ }
1048+
1049+ /// Re-derives the payer's signing keypair for payer proof creation.
1050+ ///
1051+ /// This performs the same key derivation that occurs during invoice request creation
1052+ /// with `deriving_signing_pubkey`, allowing the payer to recover their signing keypair.
1053+ /// The `nonce` and `payment_id` must be the same ones used when creating the original
1054+ /// invoice request (available from [`OffersContext::OutboundPaymentForOffer`]).
1055+ ///
1056+ /// [`OffersContext::OutboundPaymentForOffer`]: crate::blinded_path::message::OffersContext::OutboundPaymentForOffer
1057+ pub ( crate ) fn derive_payer_signing_keys < T : secp256k1:: Signing > (
1058+ & self , payment_id : PaymentId , nonce : Nonce , key : & ExpandedKey , secp_ctx : & Secp256k1 < T > ,
1059+ ) -> Result < Keypair , ( ) > {
1060+ let iv_bytes = match & self . contents {
1061+ InvoiceContents :: ForOffer { .. } => INVOICE_REQUEST_IV_BYTES ,
1062+ InvoiceContents :: ForRefund { .. } => REFUND_IV_BYTES_WITHOUT_METADATA ,
1063+ } ;
1064+ self . contents . derive_payer_signing_keys (
1065+ & self . bytes ,
1066+ payment_id,
1067+ nonce,
1068+ key,
1069+ iv_bytes,
1070+ secp_ctx,
1071+ )
1072+ }
1073+
10351074 pub ( crate ) fn as_tlv_stream ( & self ) -> FullInvoiceTlvStreamRef < ' _ > {
10361075 let (
10371076 payer_tlv_stream,
@@ -1317,20 +1356,8 @@ impl InvoiceContents {
13171356 & self , bytes : & [ u8 ] , metadata : & Metadata , key : & ExpandedKey , iv_bytes : & [ u8 ; IV_LEN ] ,
13181357 secp_ctx : & Secp256k1 < T > ,
13191358 ) -> Result < PaymentId , ( ) > {
1320- const EXPERIMENTAL_TYPES : core:: ops:: Range < u64 > =
1321- EXPERIMENTAL_OFFER_TYPES . start ..EXPERIMENTAL_INVOICE_REQUEST_TYPES . end ;
1322-
1323- let offer_records = TlvStream :: new ( bytes) . range ( OFFER_TYPES ) ;
1324- let invreq_records = TlvStream :: new ( bytes) . range ( INVOICE_REQUEST_TYPES ) . filter ( |record| {
1325- match record. r#type {
1326- PAYER_METADATA_TYPE => false , // Should be outside range
1327- INVOICE_REQUEST_PAYER_ID_TYPE => !metadata. derives_payer_keys ( ) ,
1328- _ => true ,
1329- }
1330- } ) ;
1331- let experimental_records = TlvStream :: new ( bytes) . range ( EXPERIMENTAL_TYPES ) ;
1332- let tlv_stream = offer_records. chain ( invreq_records) . chain ( experimental_records) ;
1333-
1359+ let exclude_payer_id = metadata. derives_payer_keys ( ) ;
1360+ let tlv_stream = Self :: payer_tlv_stream ( bytes, exclude_payer_id) ;
13341361 let signing_pubkey = self . payer_signing_pubkey ( ) ;
13351362 signer:: verify_payer_metadata (
13361363 metadata. as_ref ( ) ,
@@ -1342,6 +1369,46 @@ impl InvoiceContents {
13421369 )
13431370 }
13441371
1372+ fn derive_payer_signing_keys < T : secp256k1:: Signing > (
1373+ & self , bytes : & [ u8 ] , payment_id : PaymentId , nonce : Nonce , key : & ExpandedKey ,
1374+ iv_bytes : & [ u8 ; IV_LEN ] , secp_ctx : & Secp256k1 < T > ,
1375+ ) -> Result < Keypair , ( ) > {
1376+ let tlv_stream = Self :: payer_tlv_stream ( bytes, true ) ;
1377+ let signing_pubkey = self . payer_signing_pubkey ( ) ;
1378+ signer:: derive_payer_keys (
1379+ payment_id,
1380+ nonce,
1381+ key,
1382+ iv_bytes,
1383+ signing_pubkey,
1384+ tlv_stream,
1385+ secp_ctx,
1386+ )
1387+ }
1388+
1389+ /// Builds the TLV stream used for payer metadata verification and key derivation.
1390+ ///
1391+ /// When `exclude_payer_id` is true, the payer signing pubkey (type 88) is excluded
1392+ /// from the stream, which is needed when deriving payer keys.
1393+ fn payer_tlv_stream (
1394+ bytes : & [ u8 ] , exclude_payer_id : bool ,
1395+ ) -> impl core:: iter:: Iterator < Item = TlvRecord < ' _ > > {
1396+ const EXPERIMENTAL_TYPES : core:: ops:: Range < u64 > =
1397+ EXPERIMENTAL_OFFER_TYPES . start ..EXPERIMENTAL_INVOICE_REQUEST_TYPES . end ;
1398+
1399+ let offer_records = TlvStream :: new ( bytes) . range ( OFFER_TYPES ) ;
1400+ let invreq_records =
1401+ TlvStream :: new ( bytes) . range ( INVOICE_REQUEST_TYPES ) . filter ( move |record| {
1402+ match record. r#type {
1403+ PAYER_METADATA_TYPE => false ,
1404+ INVOICE_REQUEST_PAYER_ID_TYPE => !exclude_payer_id,
1405+ _ => true ,
1406+ }
1407+ } ) ;
1408+ let experimental_records = TlvStream :: new ( bytes) . range ( EXPERIMENTAL_TYPES ) ;
1409+ offer_records. chain ( invreq_records) . chain ( experimental_records)
1410+ }
1411+
13451412 fn as_tlv_stream ( & self ) -> PartialInvoiceTlvStreamRef < ' _ > {
13461413 let ( payer, offer, invoice_request, experimental_offer, experimental_invoice_request) =
13471414 match self {
0 commit comments