@@ -110,6 +110,9 @@ use crate::prelude::*;
110110/// Tag for the hash function used when signing an [`InvoiceRequest`]'s merkle root.
111111pub const SIGNATURE_TAG : & ' static str = concat ! ( "lightning" , "invoice_request" , "signature" ) ;
112112
113+ /// Tag for the hash function used when signing a BIP-353 address in an [`InvoiceRequest`].
114+ pub const BIP353_SIGNATURE_TAG : & ' static str = concat ! ( "lightning" , "invoice_request" , "invreq_payer_bip_353_signature" ) ;
115+
113116pub ( super ) const IV_BYTES : & [ u8 ; IV_LEN ] = b"LDK Invreq ~~~~~" ;
114117
115118/// Builds an [`InvoiceRequest`] from an [`Offer`] for the "offer to be paid" flow.
@@ -188,6 +191,7 @@ macro_rules! invoice_request_builder_methods { (
188191 payer: PayerContents ( metadata) , offer, chain: None , amount_msats: None ,
189192 features: InvoiceRequestFeatures :: empty( ) , quantity: None , payer_note: None ,
190193 offer_from_hrn: None , invreq_contact_secret: None , invreq_payer_offer: None ,
194+ invreq_payer_bip_353_name: None , invreq_payer_bip_353_signature: None ,
191195 #[ cfg( test) ]
192196 experimental_bar: None ,
193197 }
@@ -279,6 +283,20 @@ macro_rules! invoice_request_builder_methods { (
279283 $return_value
280284 }
281285
286+ /// Sets the BIP-353 human-readable name for the payer.
287+ ///
288+ /// This will include the serialized HumanReadableName in the invoice request.
289+ /// When set along with an offer's signing key, a BIP-353 signature will be
290+ /// created over the invoice request as specified in BLIP-42.
291+ ///
292+ /// Successive calls to this method will override the previous setting.
293+ pub fn bip353_name( $( $self_mut) * $self: $self_type, name: & HumanReadableName ) -> $return_type {
294+ let mut bytes = Vec :: new( ) ;
295+ name. write( & mut bytes) . unwrap( ) ;
296+ $self. invoice_request. invreq_payer_bip_353_name = Some ( bytes) ;
297+ $return_value
298+ }
299+
282300 fn build_with_checks( $( $self_mut) * $self: $self_type) -> Result <
283301 ( UnsignedInvoiceRequest , Option <Keypair >, Option <& ' b Secp256k1 <$secp_context>>) ,
284302 Bolt12SemanticError
@@ -549,6 +567,99 @@ impl UnsignedInvoiceRequest {
549567 pub fn tagged_hash ( & self ) -> & TaggedHash {
550568 & self . tagged_hash
551569 }
570+
571+ /// Adds a BIP-353 signature to the invoice request if a BIP-353 name is present.
572+ ///
573+ /// This method should be called after creating the unsigned invoice request and before
574+ /// calling `sign()`. The signature is created by signing the invoice request TLV stream
575+ /// (excluding the top-level signature and the BIP-353 signature itself) with the provided
576+ /// keypair, which should be the signing key for the payer's own offer.
577+ ///
578+ /// The signature proves ownership of the BIP-353 address by demonstrating possession of
579+ /// the corresponding offer's signing key.
580+ pub ( super ) fn add_bip353_signature < C : secp256k1:: Signing > (
581+ & mut self , offer_keypair : & Keypair , secp_ctx : & Secp256k1 < C >
582+ ) -> Result < ( ) , ( ) > {
583+ // Only add signature if BIP-353 name is present
584+ if self . contents . inner . invreq_payer_bip_353_name . is_none ( ) {
585+ return Ok ( ( ) ) ;
586+ }
587+
588+ // Per BLIP-42 specification:
589+ // The BIP-353 signature is computed over the invoice_request TLV stream ONLY.
590+ // This means:
591+ // - EXCLUDE payer TLVs (type 0-7)
592+ // - EXCLUDE offer TLVs (type 1-79, 80-159)
593+ // - INCLUDE invoice_request TLVs (type 80-159, 160-239)
594+ // - INCLUDE experimental invoice_request TLVs (type 2000001729, 2000001731, 2000001733)
595+ // - EXCLUDE the BIP-353 signature itself (type 2000001735)
596+ // - EXCLUDE the main signature (type 240)
597+ //
598+ // The signature tag is: "lightning" || "invoice_request" || "invreq_payer_bip_353_signature"
599+
600+ // Build the TLV stream containing ONLY invoice_request fields (excluding signature)
601+ let (
602+ _payer_tlv_stream,
603+ _offer_tlv_stream,
604+ invoice_request_tlv_stream,
605+ _experimental_offer_tlv_stream,
606+ mut experimental_invoice_request_tlv_stream,
607+ ) = self . contents . as_tlv_stream ( ) ;
608+
609+ // Clear the signature field to exclude it from the hash
610+ experimental_invoice_request_tlv_stream. invreq_payer_bip_353_signature = None ;
611+
612+ // Build the bytes to sign: only invoice_request TLVs + experimental invoice_request TLVs
613+ let mut invreq_bytes_to_sign = Vec :: new ( ) ;
614+ invoice_request_tlv_stream. write ( & mut invreq_bytes_to_sign) . unwrap ( ) ;
615+ experimental_invoice_request_tlv_stream. write ( & mut invreq_bytes_to_sign) . unwrap ( ) ;
616+
617+ // Create the tagged hash for BIP-353 signature
618+ let tlv_stream = TlvStream :: new ( & invreq_bytes_to_sign) ;
619+ let tagged_hash = TaggedHash :: from_tlv_stream ( BIP353_SIGNATURE_TAG , tlv_stream) ;
620+
621+ // Sign the hash
622+ let msg = tagged_hash. as_digest ( ) ;
623+ let signature = secp_ctx. sign_schnorr_no_aux_rand ( msg, offer_keypair) ;
624+
625+ // Encode as [33 bytes pubkey][64 bytes signature]
626+ let pubkey = offer_keypair. public_key ( ) ;
627+ let mut signature_bytes = Vec :: with_capacity ( 97 ) ;
628+ signature_bytes. extend_from_slice ( & pubkey. serialize ( ) ) ;
629+ signature_bytes. extend_from_slice ( & signature[ ..] ) ;
630+
631+ // Update the contents with the signature
632+ self . contents . inner . invreq_payer_bip_353_signature = Some ( signature_bytes. clone ( ) ) ;
633+
634+ // Rebuild the experimental bytes with the signature included.
635+ // The experimental_bytes contains: [experimental offer TLVs] + [experimental invoice request TLVs]
636+ // We need to preserve the experimental offer TLVs and update only the invoice request part.
637+
638+ // Extract experimental offer TLVs from current experimental_bytes
639+ let mut new_experimental_bytes = Vec :: new ( ) ;
640+ for record in TlvStream :: new ( & self . experimental_bytes ) . range ( EXPERIMENTAL_OFFER_TYPES ) {
641+ record. write ( & mut new_experimental_bytes) . unwrap ( ) ;
642+ }
643+
644+ // Write experimental invoice request TLVs with the signature
645+ let experimental_invoice_request_tlv_stream_with_sig = ExperimentalInvoiceRequestTlvStreamRef {
646+ invreq_contact_secret : self . contents . inner . invreq_contact_secret . as_ref ( ) ,
647+ invreq_payer_offer : self . contents . inner . invreq_payer_offer . as_ref ( ) ,
648+ invreq_payer_bip_353_name : self . contents . inner . invreq_payer_bip_353_name . as_ref ( ) ,
649+ invreq_payer_bip_353_signature : Some ( & signature_bytes) ,
650+ #[ cfg( test) ]
651+ experimental_bar : self . contents . inner . experimental_bar ,
652+ } ;
653+ experimental_invoice_request_tlv_stream_with_sig. write ( & mut new_experimental_bytes) . unwrap ( ) ;
654+
655+ self . experimental_bytes = new_experimental_bytes;
656+
657+ // Recompute the main tagged hash now that we've added the BIP-353 signature
658+ let tlv_stream = TlvStream :: new ( & self . bytes ) . chain ( TlvStream :: new ( & self . experimental_bytes ) ) ;
659+ self . tagged_hash = TaggedHash :: from_tlv_stream ( SIGNATURE_TAG , tlv_stream) ;
660+
661+ Ok ( ( ) )
662+ }
552663}
553664
554665macro_rules! unsigned_invoice_request_sign_method { (
@@ -716,6 +827,8 @@ pub(super) struct InvoiceRequestContentsWithoutPayerSigningPubkey {
716827 offer_from_hrn : Option < HumanReadableName > ,
717828 invreq_contact_secret : Option < [ u8 ; 32 ] > ,
718829 invreq_payer_offer : Option < Vec < u8 > > ,
830+ invreq_payer_bip_353_name : Option < Vec < u8 > > ,
831+ invreq_payer_bip_353_signature : Option < Vec < u8 > > ,
719832 #[ cfg( test) ]
720833 experimental_bar : Option < u64 > ,
721834}
@@ -1281,7 +1394,8 @@ impl InvoiceRequestContentsWithoutPayerSigningPubkey {
12811394 let experimental_invoice_request = ExperimentalInvoiceRequestTlvStreamRef {
12821395 invreq_contact_secret : self . invreq_contact_secret . as_ref ( ) ,
12831396 invreq_payer_offer : self . invreq_payer_offer . as_ref ( ) ,
1284- invreq_payer_bip_353_name : None ,
1397+ invreq_payer_bip_353_name : self . invreq_payer_bip_353_name . as_ref ( ) ,
1398+ invreq_payer_bip_353_signature : self . invreq_payer_bip_353_signature . as_ref ( ) ,
12851399 #[ cfg( test) ]
12861400 experimental_bar : self . experimental_bar ,
12871401 } ;
@@ -1374,6 +1488,7 @@ tlv_stream!(
13741488 ( 2_000_001_729 , invreq_contact_secret: [ u8 ; 32 ] ) ,
13751489 ( 2_000_001_731 , invreq_payer_offer: ( Vec <u8 >, WithoutLength ) ) ,
13761490 ( 2_000_001_733 , invreq_payer_bip_353_name: ( Vec <u8 >, WithoutLength ) ) ,
1491+ ( 2_000_001_735 , invreq_payer_bip_353_signature: ( Vec <u8 >, WithoutLength ) ) ,
13771492 // When adding experimental TLVs, update EXPERIMENTAL_TLV_ALLOCATION_SIZE accordingly in
13781493 // UnsignedInvoiceRequest::new to avoid unnecessary allocations.
13791494 }
@@ -1386,6 +1501,7 @@ tlv_stream!(
13861501 ( 2_000_001_729 , invreq_contact_secret: [ u8 ; 32 ] ) ,
13871502 ( 2_000_001_731 , invreq_payer_offer: ( Vec <u8 >, WithoutLength ) ) ,
13881503 ( 2_000_001_733 , invreq_payer_bip_353_name: ( Vec <u8 >, WithoutLength ) ) ,
1504+ ( 2_000_001_735 , invreq_payer_bip_353_signature: ( Vec <u8 >, WithoutLength ) ) ,
13891505 ( 2_999_999_999 , experimental_bar: ( u64 , HighZeroBytesDroppedBigSize ) ) ,
13901506 }
13911507) ;
@@ -1523,6 +1639,7 @@ impl TryFrom<PartialInvoiceRequestTlvStream> for InvoiceRequestContents {
15231639 invreq_contact_secret,
15241640 invreq_payer_offer,
15251641 invreq_payer_bip_353_name : _,
1642+ invreq_payer_bip_353_signature : _,
15261643 #[ cfg( test) ]
15271644 experimental_bar,
15281645 } ,
@@ -1568,6 +1685,8 @@ impl TryFrom<PartialInvoiceRequestTlvStream> for InvoiceRequestContents {
15681685 offer_from_hrn,
15691686 invreq_contact_secret,
15701687 invreq_payer_offer,
1688+ invreq_payer_bip_353_name : None ,
1689+ invreq_payer_bip_353_signature : None ,
15711690 #[ cfg( test) ]
15721691 experimental_bar,
15731692 } ,
@@ -1773,6 +1892,7 @@ mod tests {
17731892 invreq_contact_secret: None ,
17741893 invreq_payer_offer: None ,
17751894 invreq_payer_bip_353_name: None ,
1895+ invreq_payer_bip_353_signature: None ,
17761896 experimental_bar: None ,
17771897 } ,
17781898 ) ,
0 commit comments