@@ -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,34 @@ 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+ ///
1041+ /// The `nonce` and `payment_id` must be the same ones used when creating the original
1042+ /// invoice request. In the common proof-of-payment flow, callers can instead use
1043+ /// `PaidBolt12Invoice::prove_payer_derived` together with the `payment_id` from
1044+ /// [`Event::PaymentSent`].
1045+ ///
1046+ /// [`Event::PaymentSent`]: crate::events::Event::PaymentSent
1047+ pub fn derive_payer_signing_keys < T : secp256k1:: Signing > (
1048+ & self , payment_id : PaymentId , nonce : Nonce , key : & ExpandedKey , secp_ctx : & Secp256k1 < T > ,
1049+ ) -> Result < Keypair , ( ) > {
1050+ let iv_bytes = match & self . contents {
1051+ InvoiceContents :: ForOffer { .. } => INVOICE_REQUEST_IV_BYTES ,
1052+ InvoiceContents :: ForRefund { .. } => REFUND_IV_BYTES_WITHOUT_METADATA ,
1053+ } ;
1054+ self . contents . derive_payer_signing_keys (
1055+ & self . bytes ,
1056+ payment_id,
1057+ nonce,
1058+ key,
1059+ iv_bytes,
1060+ secp_ctx,
1061+ )
1062+ }
1063+
10351064 pub ( crate ) fn as_tlv_stream ( & self ) -> FullInvoiceTlvStreamRef < ' _ > {
10361065 let (
10371066 payer_tlv_stream,
@@ -1317,20 +1346,8 @@ impl InvoiceContents {
13171346 & self , bytes : & [ u8 ] , metadata : & Metadata , key : & ExpandedKey , iv_bytes : & [ u8 ; IV_LEN ] ,
13181347 secp_ctx : & Secp256k1 < T > ,
13191348 ) -> 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-
1349+ let exclude_payer_id = metadata. derives_payer_keys ( ) ;
1350+ let tlv_stream = Self :: payer_tlv_stream ( bytes, exclude_payer_id) ;
13341351 let signing_pubkey = self . payer_signing_pubkey ( ) ;
13351352 signer:: verify_payer_metadata (
13361353 metadata. as_ref ( ) ,
@@ -1342,6 +1359,46 @@ impl InvoiceContents {
13421359 )
13431360 }
13441361
1362+ fn derive_payer_signing_keys < T : secp256k1:: Signing > (
1363+ & self , bytes : & [ u8 ] , payment_id : PaymentId , nonce : Nonce , key : & ExpandedKey ,
1364+ iv_bytes : & [ u8 ; IV_LEN ] , secp_ctx : & Secp256k1 < T > ,
1365+ ) -> Result < Keypair , ( ) > {
1366+ let tlv_stream = Self :: payer_tlv_stream ( bytes, true ) ;
1367+ let signing_pubkey = self . payer_signing_pubkey ( ) ;
1368+ signer:: derive_payer_keys (
1369+ payment_id,
1370+ nonce,
1371+ key,
1372+ iv_bytes,
1373+ signing_pubkey,
1374+ tlv_stream,
1375+ secp_ctx,
1376+ )
1377+ }
1378+
1379+ /// Builds the TLV stream used for payer metadata verification and key derivation.
1380+ ///
1381+ /// When `exclude_payer_id` is true, the payer signing pubkey (type 88) is excluded
1382+ /// from the stream, which is needed when deriving payer keys.
1383+ fn payer_tlv_stream (
1384+ bytes : & [ u8 ] , exclude_payer_id : bool ,
1385+ ) -> impl core:: iter:: Iterator < Item = TlvRecord < ' _ > > {
1386+ const EXPERIMENTAL_TYPES : core:: ops:: Range < u64 > =
1387+ EXPERIMENTAL_OFFER_TYPES . start ..EXPERIMENTAL_INVOICE_REQUEST_TYPES . end ;
1388+
1389+ let offer_records = TlvStream :: new ( bytes) . range ( OFFER_TYPES ) ;
1390+ let invreq_records =
1391+ TlvStream :: new ( bytes) . range ( INVOICE_REQUEST_TYPES ) . filter ( move |record| {
1392+ match record. r#type {
1393+ PAYER_METADATA_TYPE => false ,
1394+ INVOICE_REQUEST_PAYER_ID_TYPE => !exclude_payer_id,
1395+ _ => true ,
1396+ }
1397+ } ) ;
1398+ let experimental_records = TlvStream :: new ( bytes) . range ( EXPERIMENTAL_TYPES ) ;
1399+ offer_records. chain ( invreq_records) . chain ( experimental_records)
1400+ }
1401+
13451402 fn as_tlv_stream ( & self ) -> PartialInvoiceTlvStreamRef < ' _ > {
13461403 let ( payer, offer, invoice_request, experimental_offer, experimental_invoice_request) =
13471404 match self {
0 commit comments