@@ -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:: {
@@ -1032,6 +1033,31 @@ impl Bolt12Invoice {
10321033 )
10331034 }
10341035
1036+ /// Re-derives the payer's signing keypair for payer proof creation.
1037+ ///
1038+ /// This performs the same key derivation that occurs during invoice request creation
1039+ /// with `deriving_signing_pubkey`, allowing the payer to recover their signing keypair.
1040+ /// The `nonce` and `payment_id` must be the same ones used when creating the original
1041+ /// invoice request (available from [`OffersContext::OutboundPaymentForOffer`]).
1042+ ///
1043+ /// [`OffersContext::OutboundPaymentForOffer`]: crate::blinded_path::message::OffersContext::OutboundPaymentForOffer
1044+ pub ( crate ) fn derive_payer_signing_keys < T : secp256k1:: Signing > (
1045+ & self , payment_id : PaymentId , nonce : Nonce , key : & ExpandedKey , secp_ctx : & Secp256k1 < T > ,
1046+ ) -> Result < Keypair , ( ) > {
1047+ let iv_bytes = match & self . contents {
1048+ InvoiceContents :: ForOffer { .. } => INVOICE_REQUEST_IV_BYTES ,
1049+ InvoiceContents :: ForRefund { .. } => REFUND_IV_BYTES_WITHOUT_METADATA ,
1050+ } ;
1051+ self . contents . derive_payer_signing_keys (
1052+ & self . bytes ,
1053+ payment_id,
1054+ nonce,
1055+ key,
1056+ iv_bytes,
1057+ secp_ctx,
1058+ )
1059+ }
1060+
10351061 pub ( crate ) fn as_tlv_stream ( & self ) -> FullInvoiceTlvStreamRef < ' _ > {
10361062 let (
10371063 payer_tlv_stream,
@@ -1317,20 +1343,8 @@ impl InvoiceContents {
13171343 & self , bytes : & [ u8 ] , metadata : & Metadata , key : & ExpandedKey , iv_bytes : & [ u8 ; IV_LEN ] ,
13181344 secp_ctx : & Secp256k1 < T > ,
13191345 ) -> 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-
1346+ let exclude_payer_id = metadata. derives_payer_keys ( ) ;
1347+ let tlv_stream = Self :: payer_tlv_stream ( bytes, exclude_payer_id) ;
13341348 let signing_pubkey = self . payer_signing_pubkey ( ) ;
13351349 signer:: verify_payer_metadata (
13361350 metadata. as_ref ( ) ,
@@ -1342,6 +1356,46 @@ impl InvoiceContents {
13421356 )
13431357 }
13441358
1359+ fn derive_payer_signing_keys < T : secp256k1:: Signing > (
1360+ & self , bytes : & [ u8 ] , payment_id : PaymentId , nonce : Nonce , key : & ExpandedKey ,
1361+ iv_bytes : & [ u8 ; IV_LEN ] , secp_ctx : & Secp256k1 < T > ,
1362+ ) -> Result < Keypair , ( ) > {
1363+ let tlv_stream = Self :: payer_tlv_stream ( bytes, true ) ;
1364+ let signing_pubkey = self . payer_signing_pubkey ( ) ;
1365+ signer:: derive_payer_keys (
1366+ payment_id,
1367+ nonce,
1368+ key,
1369+ iv_bytes,
1370+ signing_pubkey,
1371+ tlv_stream,
1372+ secp_ctx,
1373+ )
1374+ }
1375+
1376+ /// Builds the TLV stream used for payer metadata verification and key derivation.
1377+ ///
1378+ /// When `exclude_payer_id` is true, the payer signing pubkey (type 88) is excluded
1379+ /// from the stream, which is needed when deriving payer keys.
1380+ fn payer_tlv_stream (
1381+ bytes : & [ u8 ] , exclude_payer_id : bool ,
1382+ ) -> impl core:: iter:: Iterator < Item = TlvRecord < ' _ > > {
1383+ const EXPERIMENTAL_TYPES : core:: ops:: Range < u64 > =
1384+ EXPERIMENTAL_OFFER_TYPES . start ..EXPERIMENTAL_INVOICE_REQUEST_TYPES . end ;
1385+
1386+ let offer_records = TlvStream :: new ( bytes) . range ( OFFER_TYPES ) ;
1387+ let invreq_records =
1388+ TlvStream :: new ( bytes) . range ( INVOICE_REQUEST_TYPES ) . filter ( move |record| {
1389+ match record. r#type {
1390+ PAYER_METADATA_TYPE => false ,
1391+ INVOICE_REQUEST_PAYER_ID_TYPE => !exclude_payer_id,
1392+ _ => true ,
1393+ }
1394+ } ) ;
1395+ let experimental_records = TlvStream :: new ( bytes) . range ( EXPERIMENTAL_TYPES ) ;
1396+ offer_records. chain ( invreq_records) . chain ( experimental_records)
1397+ }
1398+
13451399 fn as_tlv_stream ( & self ) -> PartialInvoiceTlvStreamRef < ' _ > {
13461400 let ( payer, offer, invoice_request, experimental_offer, experimental_invoice_request) =
13471401 match self {
0 commit comments