@@ -35,14 +35,15 @@ use crate::offers::offer::{OFFER_DESCRIPTION_TYPE, OFFER_ISSUER_TYPE};
3535use crate :: offers:: parse:: Bech32Encode ;
3636use crate :: offers:: payer:: PAYER_METADATA_TYPE ;
3737use crate :: types:: payment:: { PaymentHash , PaymentPreimage } ;
38- use crate :: util:: ser:: { BigSize , Readable , Writeable } ;
38+ use crate :: util:: ser:: { BigSize , HighZeroBytesDroppedBigSize , Readable , Writeable } ;
3939use lightning_types:: string:: PrintableString ;
4040
4141use bitcoin:: hashes:: { sha256, Hash , HashEngine } ;
4242use bitcoin:: secp256k1:: schnorr:: Signature ;
4343use bitcoin:: secp256k1:: { Message , PublicKey , Secp256k1 } ;
4444
4545use core:: convert:: TryFrom ;
46+ use core:: time:: Duration ;
4647
4748#[ allow( unused_imports) ]
4849use crate :: prelude:: * ;
@@ -118,6 +119,15 @@ struct PayerProofContents {
118119 invoice_signature : Signature ,
119120 payer_signature : Signature ,
120121 payer_note : Option < String > ,
122+ disclosed_fields : DisclosedFields ,
123+ }
124+
125+ #[ derive( Clone , Debug , Default ) ]
126+ struct DisclosedFields {
127+ offer_description : Option < String > ,
128+ offer_issuer : Option < String > ,
129+ invoice_amount_msats : Option < u64 > ,
130+ invoice_created_at : Option < Duration > ,
121131}
122132
123133/// Builds a [`PayerProof`] from a paid invoice and its preimage.
@@ -243,6 +253,10 @@ impl<'a> PayerProofBuilder<'a> {
243253 for r in TlvStream :: new ( & invoice_bytes) . filter ( |r| !SIGNATURE_TYPES . contains ( & r. r#type ) ) {
244254 bytes_without_sig. extend_from_slice ( r. record_bytes ) ;
245255 }
256+ let disclosed_fields = extract_disclosed_fields (
257+ TlvStream :: new ( & invoice_bytes)
258+ . filter ( |r| self . included_types . contains ( & r. r#type ) && !SIGNATURE_TYPES . contains ( & r. r#type ) ) ,
259+ ) ?;
246260
247261 let disclosure =
248262 merkle:: compute_selective_disclosure ( & bytes_without_sig, & self . included_types ) ?;
@@ -257,6 +271,7 @@ impl<'a> PayerProofBuilder<'a> {
257271 issuer_signing_pubkey : self . invoice . signing_pubkey ( ) ,
258272 invoice_bytes,
259273 included_types : self . included_types ,
274+ disclosed_fields,
260275 disclosure,
261276 } )
262277 }
@@ -271,6 +286,7 @@ struct UnsignedPayerProof {
271286 issuer_signing_pubkey : PublicKey ,
272287 invoice_bytes : Vec < u8 > ,
273288 included_types : BTreeSet < u64 > ,
289+ disclosed_fields : DisclosedFields ,
274290 disclosure : SelectiveDisclosure ,
275291}
276292
@@ -299,6 +315,7 @@ impl UnsignedPayerProof {
299315 invoice_signature : self . invoice_signature ,
300316 payer_signature,
301317 payer_note : note. map ( String :: from) ,
318+ disclosed_fields : self . disclosed_fields ,
302319 } ,
303320 merkle_root : self . disclosure . merkle_root ,
304321 } )
@@ -426,6 +443,26 @@ impl PayerProof {
426443 self . contents . payer_signature
427444 }
428445
446+ /// The disclosed offer description, if included in the proof.
447+ pub fn offer_description ( & self ) -> Option < PrintableString < ' _ > > {
448+ self . contents . disclosed_fields . offer_description . as_deref ( ) . map ( PrintableString )
449+ }
450+
451+ /// The disclosed offer issuer, if included in the proof.
452+ pub fn offer_issuer ( & self ) -> Option < PrintableString < ' _ > > {
453+ self . contents . disclosed_fields . offer_issuer . as_deref ( ) . map ( PrintableString )
454+ }
455+
456+ /// The disclosed invoice amount, if included in the proof.
457+ pub fn invoice_amount_msats ( & self ) -> Option < u64 > {
458+ self . contents . disclosed_fields . invoice_amount_msats
459+ }
460+
461+ /// The disclosed invoice creation time, if included in the proof.
462+ pub fn invoice_created_at ( & self ) -> Option < Duration > {
463+ self . contents . disclosed_fields . invoice_created_at
464+ }
465+
429466 /// The payer's note, if any.
430467 pub fn payer_note ( & self ) -> Option < PrintableString < ' _ > > {
431468 self . contents . payer_note . as_deref ( ) . map ( PrintableString )
@@ -473,6 +510,47 @@ fn validate_tlv_framing(bytes: &[u8]) -> Result<(), crate::ln::msgs::DecodeError
473510 Ok ( ( ) )
474511}
475512
513+ fn update_disclosed_fields (
514+ record : & crate :: offers:: merkle:: TlvRecord < ' _ > , disclosed_fields : & mut DisclosedFields ,
515+ ) -> Result < ( ) , crate :: ln:: msgs:: DecodeError > {
516+ use crate :: ln:: msgs:: DecodeError ;
517+
518+ match record. r#type {
519+ OFFER_DESCRIPTION_TYPE => {
520+ disclosed_fields. offer_description = Some (
521+ String :: from_utf8 ( record. value_bytes . to_vec ( ) ) . map_err ( |_| DecodeError :: InvalidValue ) ?,
522+ ) ;
523+ } ,
524+ OFFER_ISSUER_TYPE => {
525+ disclosed_fields. offer_issuer = Some (
526+ String :: from_utf8 ( record. value_bytes . to_vec ( ) ) . map_err ( |_| DecodeError :: InvalidValue ) ?,
527+ ) ;
528+ } ,
529+ INVOICE_CREATED_AT_TYPE => {
530+ disclosed_fields. invoice_created_at = Some ( Duration :: from_secs (
531+ record. read_value :: < HighZeroBytesDroppedBigSize < u64 > > ( ) ?. 0 ,
532+ ) ) ;
533+ } ,
534+ INVOICE_AMOUNT_TYPE => {
535+ disclosed_fields. invoice_amount_msats =
536+ Some ( record. read_value :: < HighZeroBytesDroppedBigSize < u64 > > ( ) ?. 0 ) ;
537+ } ,
538+ _ => { } ,
539+ }
540+
541+ Ok ( ( ) )
542+ }
543+
544+ fn extract_disclosed_fields < ' a > (
545+ records : impl core:: iter:: Iterator < Item = crate :: offers:: merkle:: TlvRecord < ' a > > ,
546+ ) -> Result < DisclosedFields , crate :: ln:: msgs:: DecodeError > {
547+ let mut disclosed_fields = DisclosedFields :: default ( ) ;
548+ for record in records {
549+ update_disclosed_fields ( & record, & mut disclosed_fields) ?;
550+ }
551+ Ok ( disclosed_fields)
552+ }
553+
476554// Payer proofs use manual TLV parsing rather than `ParsedMessage` / `tlv_stream!`
477555// because of their hybrid structure: a dynamic, variable set of included invoice
478556// TLV records (types 0-239, preserved as raw bytes for merkle reconstruction) plus
@@ -502,6 +580,7 @@ impl TryFrom<Vec<u8>> for PayerProof {
502580 let mut preimage: Option < PaymentPreimage > = None ;
503581 let mut payer_signature: Option < Signature > = None ;
504582 let mut payer_note: Option < String > = None ;
583+ let mut disclosed_fields = DisclosedFields :: default ( ) ;
505584
506585 let mut leaf_hashes: Vec < sha256:: Hash > = Vec :: new ( ) ;
507586 let mut omitted_markers: Vec < u64 > = Vec :: new ( ) ;
@@ -522,6 +601,7 @@ impl TryFrom<Vec<u8>> for PayerProof {
522601 }
523602 }
524603 prev_tlv_type = Some ( tlv_type) ;
604+ update_disclosed_fields ( & record, & mut disclosed_fields) ?;
525605
526606 match tlv_type {
527607 INVOICE_REQUEST_PAYER_ID_TYPE => {
@@ -677,17 +757,18 @@ impl TryFrom<Vec<u8>> for PayerProof {
677757
678758 Ok ( PayerProof {
679759 bytes,
680- contents : PayerProofContents {
681- payer_id,
682- payment_hash,
683- issuer_signing_pubkey,
684- preimage,
685- invoice_signature,
686- payer_signature,
687- payer_note,
688- } ,
689- merkle_root,
690- } )
760+ contents : PayerProofContents {
761+ payer_id,
762+ payment_hash,
763+ issuer_signing_pubkey,
764+ preimage,
765+ invoice_signature,
766+ payer_signature,
767+ payer_note,
768+ disclosed_fields,
769+ } ,
770+ merkle_root,
771+ } )
691772 }
692773}
693774
@@ -834,6 +915,10 @@ mod tests {
834915 ]
835916 . into_iter ( )
836917 . collect ( ) ;
918+ let disclosed_fields = extract_disclosed_fields (
919+ TlvStream :: new ( & invoice_bytes) . filter ( |r| included_types. contains ( & r. r#type ) ) ,
920+ )
921+ . unwrap ( ) ;
837922 let disclosure = compute_selective_disclosure ( & invoice_bytes, & included_types) . unwrap ( ) ;
838923
839924 let unsigned = UnsignedPayerProof {
@@ -844,6 +929,7 @@ mod tests {
844929 issuer_signing_pubkey,
845930 invoice_bytes,
846931 included_types,
932+ disclosed_fields,
847933 disclosure,
848934 } ;
849935
@@ -882,6 +968,10 @@ mod tests {
882968 [ INVOICE_REQUEST_PAYER_ID_TYPE , INVOICE_PAYMENT_HASH_TYPE , INVOICE_NODE_ID_TYPE ]
883969 . into_iter ( )
884970 . collect ( ) ;
971+ let disclosed_fields = extract_disclosed_fields (
972+ TlvStream :: new ( & invoice_bytes) . filter ( |r| included_types. contains ( & r. r#type ) ) ,
973+ )
974+ . unwrap ( ) ;
885975 let disclosure = compute_selective_disclosure ( & invoice_bytes, & included_types) . unwrap ( ) ;
886976 assert_eq ! ( disclosure. omitted_markers, vec![ 177 , 178 ] ) ;
887977
@@ -893,6 +983,7 @@ mod tests {
893983 issuer_signing_pubkey,
894984 invoice_bytes,
895985 included_types,
986+ disclosed_fields,
896987 disclosure,
897988 } ;
898989
@@ -948,6 +1039,10 @@ mod tests {
9481039 ]
9491040 . into_iter ( )
9501041 . collect ( ) ;
1042+ let disclosed_fields = extract_disclosed_fields (
1043+ TlvStream :: new ( & invoice_bytes) . filter ( |r| included_types. contains ( & r. r#type ) ) ,
1044+ )
1045+ . unwrap ( ) ;
9511046 let disclosure = compute_selective_disclosure ( & invoice_bytes, & included_types) . unwrap ( ) ;
9521047
9531048 let unsigned = UnsignedPayerProof {
@@ -958,6 +1053,7 @@ mod tests {
9581053 issuer_signing_pubkey,
9591054 invoice_bytes,
9601055 included_types,
1056+ disclosed_fields,
9611057 disclosure,
9621058 } ;
9631059
0 commit comments