@@ -75,9 +75,11 @@ use core::str::FromStr;
75
75
use core:: time:: Duration ;
76
76
use crate :: io;
77
77
use crate :: ln:: features:: OfferFeatures ;
78
+ use crate :: ln:: inbound_payment:: { ExpandedKey , Nonce } ;
78
79
use crate :: ln:: msgs:: MAX_VALUE_MSAT ;
79
80
use crate :: offers:: invoice_request:: InvoiceRequestBuilder ;
80
81
use crate :: offers:: parse:: { Bech32Encode , ParseError , ParsedMessage , SemanticError } ;
82
+ use crate :: offers:: signer:: { MetadataMaterial , DerivedPubkey } ;
81
83
use crate :: onion_message:: BlindedPath ;
82
84
use crate :: util:: ser:: { HighZeroBytesDroppedBigSize , WithoutLength , Writeable , Writer } ;
83
85
use crate :: util:: string:: PrintableString ;
@@ -94,6 +96,7 @@ use std::time::SystemTime;
94
96
/// [module-level documentation]: self
95
97
pub struct OfferBuilder {
96
98
offer : OfferContents ,
99
+ metadata_material : Option < MetadataMaterial > ,
97
100
}
98
101
99
102
impl OfferBuilder {
@@ -108,7 +111,28 @@ impl OfferBuilder {
108
111
features : OfferFeatures :: empty ( ) , absolute_expiry : None , issuer : None , paths : None ,
109
112
supported_quantity : Quantity :: One , signing_pubkey,
110
113
} ;
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) }
112
136
}
113
137
114
138
/// Adds the chain hash of the given [`Network`] to [`Offer::chains`]. If not called,
@@ -127,12 +151,38 @@ impl OfferBuilder {
127
151
self
128
152
}
129
153
130
- /// Sets the [`Offer::metadata`].
154
+ /// Sets the [`Offer::metadata`] to the given bytes .
131
155
///
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
+
134
163
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 )
136
186
}
137
187
138
188
/// Sets the [`Offer::amount`] as an [`Amount::Bitcoin`].
@@ -204,6 +254,16 @@ impl OfferBuilder {
204
254
}
205
255
}
206
256
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
+
207
267
let mut bytes = Vec :: new ( ) ;
208
268
self . offer . write ( & mut bytes) . unwrap ( ) ;
209
269
@@ -764,15 +824,15 @@ mod tests {
764
824
#[ test]
765
825
fn builds_offer_with_metadata ( ) {
766
826
let offer = OfferBuilder :: new ( "foo" . into ( ) , pubkey ( 42 ) )
767
- . metadata ( vec ! [ 42 ; 32 ] )
827
+ . metadata ( vec ! [ 42 ; 32 ] ) . unwrap ( )
768
828
. build ( )
769
829
. unwrap ( ) ;
770
830
assert_eq ! ( offer. metadata( ) , Some ( & vec![ 42 ; 32 ] ) ) ;
771
831
assert_eq ! ( offer. as_tlv_stream( ) . metadata, Some ( & vec![ 42 ; 32 ] ) ) ;
772
832
773
833
let offer = OfferBuilder :: new ( "foo" . into ( ) , pubkey ( 42 ) )
774
- . metadata ( vec ! [ 42 ; 32 ] )
775
- . metadata ( vec ! [ 43 ; 32 ] )
834
+ . metadata ( vec ! [ 42 ; 32 ] ) . unwrap ( )
835
+ . metadata ( vec ! [ 43 ; 32 ] ) . unwrap ( )
776
836
. build ( )
777
837
. unwrap ( ) ;
778
838
assert_eq ! ( offer. metadata( ) , Some ( & vec![ 43 ; 32 ] ) ) ;
0 commit comments