@@ -76,8 +76,8 @@ use crate::offers::offer::{Offer, OfferContents, OfferId, OfferTlvStream, OfferT
7676use crate :: offers:: parse:: { Bolt12ParseError , ParsedMessage , Bolt12SemanticError } ;
7777use crate :: offers:: payer:: { PayerContents , PayerTlvStream , PayerTlvStreamRef } ;
7878use crate :: offers:: signer:: { Metadata , MetadataMaterial } ;
79- use crate :: util:: ser:: { HighZeroBytesDroppedBigSize , SeekReadable , WithoutLength , Writeable , Writer } ;
80- use crate :: util:: string:: PrintableString ;
79+ use crate :: util:: ser:: { HighZeroBytesDroppedBigSize , Readable , SeekReadable , WithoutLength , Writeable , Writer } ;
80+ use crate :: util:: string:: { PrintableString , UntrustedString } ;
8181
8282#[ cfg( not( c_bindings) ) ]
8383use {
@@ -872,6 +872,24 @@ impl VerifiedInvoiceRequest {
872872 invoice_request_respond_with_derived_signing_pubkey_methods ! ( self , self . inner, InvoiceBuilder <DerivedSigningPubkey >) ;
873873 #[ cfg( c_bindings) ]
874874 invoice_request_respond_with_derived_signing_pubkey_methods ! ( self , self . inner, InvoiceWithDerivedSigningPubkeyBuilder ) ;
875+
876+ pub ( crate ) fn fields ( & self ) -> InvoiceRequestFields {
877+ let InvoiceRequestContents {
878+ payer_id,
879+ inner : InvoiceRequestContentsWithoutPayerId {
880+ payer : _, offer : _, chain : _, amount_msats, features, quantity, payer_note
881+ } ,
882+ } = & self . inner . contents ;
883+
884+ InvoiceRequestFields {
885+ payer_id : * payer_id,
886+ amount_msats : * amount_msats,
887+ features : features. clone ( ) ,
888+ quantity : * quantity,
889+ payer_note_truncated : payer_note. clone ( )
890+ . map ( |mut s| { s. truncate ( PAYER_NOTE_LIMIT ) ; UntrustedString ( s) } ) ,
891+ }
892+ }
875893}
876894
877895impl InvoiceRequestContents {
@@ -1100,9 +1118,68 @@ impl TryFrom<PartialInvoiceRequestTlvStream> for InvoiceRequestContents {
11001118 }
11011119}
11021120
1121+ /// Fields sent in an [`InvoiceRequest`] message to include in [`PaymentContext::Bolt12Offer`].
1122+ ///
1123+ /// [`PaymentContext::Bolt12Offer`]: crate::blinded_path::payment::PaymentContext::Bolt12Offer
1124+ #[ derive( Clone , Debug , Eq , PartialEq ) ]
1125+ pub struct InvoiceRequestFields {
1126+ /// A possibly transient pubkey used to sign the invoice request.
1127+ pub payer_id : PublicKey ,
1128+
1129+ /// The amount to pay in msats (i.e., the minimum lightning-payable unit for [`chain`]), which
1130+ /// must be greater than or equal to [`Offer::amount`], converted if necessary.
1131+ ///
1132+ /// [`chain`]: InvoiceRequest::chain
1133+ pub amount_msats : Option < u64 > ,
1134+
1135+ /// Features pertaining to requesting an invoice.
1136+ pub features : InvoiceRequestFeatures ,
1137+
1138+ /// The quantity of the offer's item conforming to [`Offer::is_valid_quantity`].
1139+ pub quantity : Option < u64 > ,
1140+
1141+ /// A payer-provided note which will be seen by the recipient and reflected back in the invoice
1142+ /// response. Truncated to [`PAYER_NOTE_LIMIT`] characters.
1143+ pub payer_note_truncated : Option < UntrustedString > ,
1144+ }
1145+
1146+ /// The maximum number of characters included in [`InvoiceRequestFields::payer_note_truncated`].
1147+ pub const PAYER_NOTE_LIMIT : usize = 512 ;
1148+
1149+ impl Writeable for InvoiceRequestFields {
1150+ fn write < W : Writer > ( & self , writer : & mut W ) -> Result < ( ) , io:: Error > {
1151+ write_tlv_fields ! ( writer, {
1152+ ( 0 , self . payer_id, required) ,
1153+ ( 2 , self . amount_msats. map( |v| HighZeroBytesDroppedBigSize ( v) ) , option) ,
1154+ ( 4 , WithoutLength ( & self . features) , required) ,
1155+ ( 6 , self . quantity. map( |v| HighZeroBytesDroppedBigSize ( v) ) , option) ,
1156+ ( 8 , self . payer_note_truncated. as_ref( ) . map( |s| WithoutLength ( & s. 0 ) ) , option) ,
1157+ } ) ;
1158+ Ok ( ( ) )
1159+ }
1160+ }
1161+
1162+ impl Readable for InvoiceRequestFields {
1163+ fn read < R : io:: Read > ( reader : & mut R ) -> Result < Self , DecodeError > {
1164+ _init_and_read_len_prefixed_tlv_fields ! ( reader, {
1165+ ( 0 , payer_id, required) ,
1166+ ( 2 , amount_msats, ( option, encoding: ( u64 , HighZeroBytesDroppedBigSize ) ) ) ,
1167+ ( 4 , features, ( option, encoding: ( InvoiceRequestFeatures , WithoutLength ) ) ) ,
1168+ ( 6 , quantity, ( option, encoding: ( u64 , HighZeroBytesDroppedBigSize ) ) ) ,
1169+ ( 8 , payer_note_truncated, ( option, encoding: ( String , WithoutLength ) ) ) ,
1170+ } ) ;
1171+ let features = features. unwrap_or ( InvoiceRequestFeatures :: empty ( ) ) ;
1172+
1173+ Ok ( InvoiceRequestFields {
1174+ payer_id : payer_id. 0 . unwrap ( ) , amount_msats, features, quantity,
1175+ payer_note_truncated : payer_note_truncated. map ( |s| UntrustedString ( s) ) ,
1176+ } )
1177+ }
1178+ }
1179+
11031180#[ cfg( test) ]
11041181mod tests {
1105- use super :: { InvoiceRequest , InvoiceRequestTlvStreamRef , SIGNATURE_TAG , UnsignedInvoiceRequest } ;
1182+ use super :: { InvoiceRequest , InvoiceRequestFields , InvoiceRequestTlvStreamRef , PAYER_NOTE_LIMIT , SIGNATURE_TAG , UnsignedInvoiceRequest } ;
11061183
11071184 use bitcoin:: blockdata:: constants:: ChainHash ;
11081185 use bitcoin:: network:: constants:: Network ;
@@ -1129,8 +1206,8 @@ mod tests {
11291206 use crate :: offers:: parse:: { Bolt12ParseError , Bolt12SemanticError } ;
11301207 use crate :: offers:: payer:: PayerTlvStreamRef ;
11311208 use crate :: offers:: test_utils:: * ;
1132- use crate :: util:: ser:: { BigSize , Writeable } ;
1133- use crate :: util:: string:: PrintableString ;
1209+ use crate :: util:: ser:: { BigSize , Readable , Writeable } ;
1210+ use crate :: util:: string:: { PrintableString , UntrustedString } ;
11341211
11351212 #[ test]
11361213 fn builds_invoice_request_with_defaults ( ) {
@@ -2166,4 +2243,55 @@ mod tests {
21662243 Err ( e) => assert_eq ! ( e, Bolt12ParseError :: Decode ( DecodeError :: InvalidValue ) ) ,
21672244 }
21682245 }
2246+
2247+ #[ test]
2248+ fn copies_verified_invoice_request_fields ( ) {
2249+ let desc = "foo" . to_string ( ) ;
2250+ let node_id = recipient_pubkey ( ) ;
2251+ let expanded_key = ExpandedKey :: new ( & KeyMaterial ( [ 42 ; 32 ] ) ) ;
2252+ let entropy = FixedEntropy { } ;
2253+ let secp_ctx = Secp256k1 :: new ( ) ;
2254+
2255+ #[ cfg( c_bindings) ]
2256+ use crate :: offers:: offer:: OfferWithDerivedMetadataBuilder as OfferBuilder ;
2257+ let offer = OfferBuilder
2258+ :: deriving_signing_pubkey ( desc, node_id, & expanded_key, & entropy, & secp_ctx)
2259+ . chain ( Network :: Testnet )
2260+ . amount_msats ( 1000 )
2261+ . supported_quantity ( Quantity :: Unbounded )
2262+ . build ( ) . unwrap ( ) ;
2263+ assert_eq ! ( offer. signing_pubkey( ) , node_id) ;
2264+
2265+ let invoice_request = offer. request_invoice ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) ) . unwrap ( )
2266+ . chain ( Network :: Testnet ) . unwrap ( )
2267+ . amount_msats ( 1001 ) . unwrap ( )
2268+ . quantity ( 1 ) . unwrap ( )
2269+ . payer_note ( "0" . repeat ( PAYER_NOTE_LIMIT * 2 ) )
2270+ . build ( ) . unwrap ( )
2271+ . sign ( payer_sign) . unwrap ( ) ;
2272+ match invoice_request. verify ( & expanded_key, & secp_ctx) {
2273+ Ok ( invoice_request) => {
2274+ let fields = invoice_request. fields ( ) ;
2275+ assert_eq ! ( invoice_request. offer_id, offer. id( ) ) ;
2276+ assert_eq ! (
2277+ fields,
2278+ InvoiceRequestFields {
2279+ payer_id: payer_pubkey( ) ,
2280+ amount_msats: Some ( 1001 ) ,
2281+ features: InvoiceRequestFeatures :: empty( ) ,
2282+ quantity: Some ( 1 ) ,
2283+ payer_note_truncated: Some ( UntrustedString ( "0" . repeat( PAYER_NOTE_LIMIT ) ) ) ,
2284+ }
2285+ ) ;
2286+
2287+ let mut buffer = Vec :: new ( ) ;
2288+ fields. write ( & mut buffer) . unwrap ( ) ;
2289+
2290+ let deserialized_fields: InvoiceRequestFields =
2291+ Readable :: read ( & mut buffer. as_slice ( ) ) . unwrap ( ) ;
2292+ assert_eq ! ( deserialized_fields, fields) ;
2293+ } ,
2294+ Err ( _) => panic ! ( "unexpected error" ) ,
2295+ }
2296+ }
21692297}
0 commit comments