@@ -75,9 +75,11 @@ use core::str::FromStr;
7575use core:: time:: Duration ;
7676use crate :: io;
7777use crate :: ln:: features:: OfferFeatures ;
78+ use crate :: ln:: inbound_payment:: { ExpandedKey , Nonce } ;
7879use crate :: ln:: msgs:: MAX_VALUE_MSAT ;
7980use crate :: offers:: invoice_request:: InvoiceRequestBuilder ;
8081use crate :: offers:: parse:: { Bech32Encode , ParseError , ParsedMessage , SemanticError } ;
82+ use crate :: offers:: signer:: { MetadataMaterial , DerivedPubkey } ;
8183use crate :: onion_message:: BlindedPath ;
8284use crate :: util:: ser:: { HighZeroBytesDroppedBigSize , WithoutLength , Writeable , Writer } ;
8385use crate :: util:: string:: PrintableString ;
@@ -94,6 +96,7 @@ use std::time::SystemTime;
9496/// [module-level documentation]: self
9597pub struct OfferBuilder {
9698 offer : OfferContents ,
99+ metadata_material : Option < MetadataMaterial > ,
97100}
98101
99102impl OfferBuilder {
@@ -108,7 +111,28 @@ impl OfferBuilder {
108111 features : OfferFeatures :: empty ( ) , absolute_expiry : None , issuer : None , paths : None ,
109112 supported_quantity : Quantity :: One , signing_pubkey,
110113 } ;
111- OfferBuilder { offer }
114+ OfferBuilder { offer, metadata_material : None }
115+ }
116+
117+ /// Similar to [`OfferBuilder::new`] except it:
118+ /// - derives the signing pubkey such that a different key can be used for each offer, and
119+ /// - sets the metadata when [`OfferBuilder::build`] is called such that it can be used by
120+ /// [`InvoiceRequest::verify`] to determine if the request was produced using a base
121+ /// [`ExpandedKey`] from which the signing pubkey was derived.
122+ ///
123+ /// [`InvoiceRequest::verify`]: crate::offers::invoice_request::InvoiceRequest::verify
124+ /// [`ExpandedKey`]: crate::ln::inbound_payment::ExpandedKey
125+ #[ allow( unused) ]
126+ pub ( crate ) fn deriving_signing_pubkey (
127+ description : String , signing_pubkey : DerivedPubkey
128+ ) -> Self {
129+ let ( signing_pubkey, metadata_material) = signing_pubkey. into_parts ( ) ;
130+ let offer = OfferContents {
131+ chains : None , metadata : None , amount : None , description,
132+ features : OfferFeatures :: empty ( ) , absolute_expiry : None , issuer : None , paths : None ,
133+ supported_quantity : Quantity :: One , signing_pubkey,
134+ } ;
135+ OfferBuilder { offer, metadata_material : Some ( metadata_material) }
112136 }
113137
114138 /// Adds the chain hash of the given [`Network`] to [`Offer::chains`]. If not called,
@@ -127,12 +151,38 @@ impl OfferBuilder {
127151 self
128152 }
129153
130- /// Sets the [`Offer::metadata`].
154+ /// Sets the [`Offer::metadata`] to the given bytes .
131155 ///
132- /// Successive calls to this method will override the previous setting.
133- pub fn metadata ( mut self , metadata : Vec < u8 > ) -> Self {
156+ /// Successive calls to this method will override the previous setting. Errors if the builder
157+ /// was constructed using a derived pubkey.
158+ pub fn metadata ( mut self , metadata : Vec < u8 > ) -> Result < Self , SemanticError > {
159+ if self . metadata_material . is_some ( ) {
160+ return Err ( SemanticError :: UnexpectedMetadata ) ;
161+ }
162+
134163 self . offer . metadata = Some ( metadata) ;
135- self
164+ Ok ( self )
165+ }
166+
167+ /// Sets the [`Offer::metadata`] derived from the given `key` and any fields set prior to
168+ /// calling [`OfferBuilder::build`]. Allows for stateless verification of an [`InvoiceRequest`]
169+ /// when using a public node id as the [`Offer::signing_pubkey`] instead of a derived one.
170+ ///
171+ /// Errors if already called or if the builder was constructed with
172+ /// [`Self::deriving_signing_pubkey`].
173+ ///
174+ /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
175+ #[ allow( unused) ]
176+ pub ( crate ) fn metadata_derived (
177+ mut self , key : & ExpandedKey , nonce : Nonce
178+ ) -> Result < Self , SemanticError > {
179+ if self . metadata_material . is_some ( ) {
180+ return Err ( SemanticError :: UnexpectedMetadata ) ;
181+ }
182+
183+ self . offer . metadata = None ;
184+ self . metadata_material = Some ( MetadataMaterial :: new ( nonce, key) ) ;
185+ Ok ( self )
136186 }
137187
138188 /// Sets the [`Offer::amount`] as an [`Amount::Bitcoin`].
@@ -204,6 +254,16 @@ impl OfferBuilder {
204254 }
205255 }
206256
257+ // Create the metadata for stateless verification of an InvoiceRequest.
258+ if let Some ( mut metadata_material) = self . metadata_material {
259+ debug_assert ! ( self . offer. metadata. is_none( ) ) ;
260+ let mut tlv_stream = self . offer . as_tlv_stream ( ) ;
261+ tlv_stream. node_id = None ;
262+ tlv_stream. write ( & mut metadata_material) . unwrap ( ) ;
263+
264+ self . offer . metadata = Some ( metadata_material. into_metadata ( ) ) ;
265+ }
266+
207267 let mut bytes = Vec :: new ( ) ;
208268 self . offer . write ( & mut bytes) . unwrap ( ) ;
209269
@@ -765,15 +825,15 @@ mod tests {
765825 #[ test]
766826 fn builds_offer_with_metadata ( ) {
767827 let offer = OfferBuilder :: new ( "foo" . into ( ) , pubkey ( 42 ) )
768- . metadata ( vec ! [ 42 ; 32 ] )
828+ . metadata ( vec ! [ 42 ; 32 ] ) . unwrap ( )
769829 . build ( )
770830 . unwrap ( ) ;
771831 assert_eq ! ( offer. metadata( ) , Some ( & vec![ 42 ; 32 ] ) ) ;
772832 assert_eq ! ( offer. as_tlv_stream( ) . metadata, Some ( & vec![ 42 ; 32 ] ) ) ;
773833
774834 let offer = OfferBuilder :: new ( "foo" . into ( ) , pubkey ( 42 ) )
775- . metadata ( vec ! [ 42 ; 32 ] )
776- . metadata ( vec ! [ 43 ; 32 ] )
835+ . metadata ( vec ! [ 42 ; 32 ] ) . unwrap ( )
836+ . metadata ( vec ! [ 43 ; 32 ] ) . unwrap ( )
777837 . build ( )
778838 . unwrap ( ) ;
779839 assert_eq ! ( offer. metadata( ) , Some ( & vec![ 43 ; 32 ] ) ) ;
0 commit comments