@@ -76,8 +76,8 @@ use crate::offers::offer::{Offer, OfferContents, OfferId, OfferTlvStream, OfferT
76
76
use crate :: offers:: parse:: { Bolt12ParseError , ParsedMessage , Bolt12SemanticError } ;
77
77
use crate :: offers:: payer:: { PayerContents , PayerTlvStream , PayerTlvStreamRef } ;
78
78
use 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 } ;
81
81
82
82
#[ cfg( not( c_bindings) ) ]
83
83
use {
@@ -872,6 +872,24 @@ impl VerifiedInvoiceRequest {
872
872
invoice_request_respond_with_derived_signing_pubkey_methods ! ( self , self . inner, InvoiceBuilder <DerivedSigningPubkey >) ;
873
873
#[ cfg( c_bindings) ]
874
874
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
+ }
875
893
}
876
894
877
895
impl InvoiceRequestContents {
@@ -1100,9 +1118,68 @@ impl TryFrom<PartialInvoiceRequestTlvStream> for InvoiceRequestContents {
1100
1118
}
1101
1119
}
1102
1120
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
+
1103
1180
#[ cfg( test) ]
1104
1181
mod tests {
1105
- use super :: { InvoiceRequest , InvoiceRequestTlvStreamRef , SIGNATURE_TAG , UnsignedInvoiceRequest } ;
1182
+ use super :: { InvoiceRequest , InvoiceRequestFields , InvoiceRequestTlvStreamRef , PAYER_NOTE_LIMIT , SIGNATURE_TAG , UnsignedInvoiceRequest } ;
1106
1183
1107
1184
use bitcoin:: blockdata:: constants:: ChainHash ;
1108
1185
use bitcoin:: network:: constants:: Network ;
@@ -1129,8 +1206,8 @@ mod tests {
1129
1206
use crate :: offers:: parse:: { Bolt12ParseError , Bolt12SemanticError } ;
1130
1207
use crate :: offers:: payer:: PayerTlvStreamRef ;
1131
1208
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 } ;
1134
1211
1135
1212
#[ test]
1136
1213
fn builds_invoice_request_with_defaults ( ) {
@@ -2166,4 +2243,55 @@ mod tests {
2166
2243
Err ( e) => assert_eq ! ( e, Bolt12ParseError :: Decode ( DecodeError :: InvalidValue ) ) ,
2167
2244
}
2168
2245
}
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
+ }
2169
2297
}
0 commit comments