@@ -97,11 +97,11 @@ use bitcoin::blockdata::constants::ChainHash;
9797use bitcoin:: hash_types:: { WPubkeyHash , WScriptHash } ;
9898use bitcoin:: hashes:: Hash ;
9999use bitcoin:: network:: constants:: Network ;
100- use bitcoin:: secp256k1:: { Message , PublicKey , Secp256k1 , self } ;
100+ use bitcoin:: secp256k1:: { KeyPair , Message , PublicKey , Secp256k1 , self } ;
101101use bitcoin:: secp256k1:: schnorr:: Signature ;
102102use bitcoin:: util:: address:: { Address , Payload , WitnessVersion } ;
103103use bitcoin:: util:: schnorr:: TweakedPublicKey ;
104- use core:: convert:: TryFrom ;
104+ use core:: convert:: { Infallible , TryFrom } ;
105105use core:: time:: Duration ;
106106use crate :: io;
107107use crate :: ln:: PaymentHash ;
@@ -136,28 +136,31 @@ pub(super) const SIGNATURE_TAG: &'static str = concat!("lightning", "invoice", "
136136/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
137137/// [`Refund`]: crate::offers::refund::Refund
138138/// [module-level documentation]: self
139- pub struct InvoiceBuilder < ' a > {
139+ pub struct InvoiceBuilder < ' a , S : SigningPubkeyStrategy > {
140140 invreq_bytes : & ' a Vec < u8 > ,
141141 invoice : InvoiceContents ,
142+ keys : Option < KeyPair > ,
143+ signing_pubkey_strategy : core:: marker:: PhantomData < S > ,
142144}
143145
144- impl < ' a > InvoiceBuilder < ' a > {
146+ /// Indicates how [`Invoice::signing_pubkey`] was set.
147+ pub trait SigningPubkeyStrategy { }
148+
149+ /// [`Invoice::signing_pubkey`] was explicitly set.
150+ pub struct ExplicitSigningPubkey { }
151+
152+ /// [`Invoice::signing_pubkey`] was derived.
153+ pub struct DerivedSigningPubkey { }
154+
155+ impl SigningPubkeyStrategy for ExplicitSigningPubkey { }
156+ impl SigningPubkeyStrategy for DerivedSigningPubkey { }
157+
158+ impl < ' a > InvoiceBuilder < ' a , ExplicitSigningPubkey > {
145159 pub ( super ) fn for_offer (
146160 invoice_request : & ' a InvoiceRequest , payment_paths : Vec < ( BlindedPath , BlindedPayInfo ) > ,
147161 created_at : Duration , payment_hash : PaymentHash
148162 ) -> Result < Self , SemanticError > {
149- let amount_msats = match invoice_request. amount_msats ( ) {
150- Some ( amount_msats) => amount_msats,
151- None => match invoice_request. contents . inner . offer . amount ( ) {
152- Some ( Amount :: Bitcoin { amount_msats } ) => {
153- amount_msats. checked_mul ( invoice_request. quantity ( ) . unwrap_or ( 1 ) )
154- . ok_or ( SemanticError :: InvalidAmount ) ?
155- } ,
156- Some ( Amount :: Currency { .. } ) => return Err ( SemanticError :: UnsupportedCurrency ) ,
157- None => return Err ( SemanticError :: MissingAmount ) ,
158- } ,
159- } ;
160-
163+ let amount_msats = Self :: check_amount_msats ( invoice_request) ?;
161164 let contents = InvoiceContents :: ForOffer {
162165 invoice_request : invoice_request. contents . clone ( ) ,
163166 fields : InvoiceFields {
@@ -167,7 +170,7 @@ impl<'a> InvoiceBuilder<'a> {
167170 } ,
168171 } ;
169172
170- Self :: new ( & invoice_request. bytes , contents)
173+ Self :: new ( & invoice_request. bytes , contents, None )
171174 }
172175
173176 pub ( super ) fn for_refund (
@@ -183,15 +186,57 @@ impl<'a> InvoiceBuilder<'a> {
183186 } ,
184187 } ;
185188
186- Self :: new ( & refund. bytes , contents)
189+ Self :: new ( & refund. bytes , contents, None )
187190 }
191+ }
188192
189- fn new ( invreq_bytes : & ' a Vec < u8 > , contents : InvoiceContents ) -> Result < Self , SemanticError > {
193+ impl < ' a > InvoiceBuilder < ' a , DerivedSigningPubkey > {
194+ pub ( super ) fn for_offer_using_keys (
195+ invoice_request : & ' a InvoiceRequest , payment_paths : Vec < ( BlindedPath , BlindedPayInfo ) > ,
196+ created_at : Duration , payment_hash : PaymentHash , keys : KeyPair
197+ ) -> Result < Self , SemanticError > {
198+ let amount_msats = Self :: check_amount_msats ( invoice_request) ?;
199+ let contents = InvoiceContents :: ForOffer {
200+ invoice_request : invoice_request. contents . clone ( ) ,
201+ fields : InvoiceFields {
202+ payment_paths, created_at, relative_expiry : None , payment_hash, amount_msats,
203+ fallbacks : None , features : Bolt12InvoiceFeatures :: empty ( ) ,
204+ signing_pubkey : invoice_request. contents . inner . offer . signing_pubkey ( ) ,
205+ } ,
206+ } ;
207+
208+ Self :: new ( & invoice_request. bytes , contents, Some ( keys) )
209+ }
210+ }
211+
212+ impl < ' a , S : SigningPubkeyStrategy > InvoiceBuilder < ' a , S > {
213+ fn check_amount_msats ( invoice_request : & InvoiceRequest ) -> Result < u64 , SemanticError > {
214+ match invoice_request. amount_msats ( ) {
215+ Some ( amount_msats) => Ok ( amount_msats) ,
216+ None => match invoice_request. contents . inner . offer . amount ( ) {
217+ Some ( Amount :: Bitcoin { amount_msats } ) => {
218+ amount_msats. checked_mul ( invoice_request. quantity ( ) . unwrap_or ( 1 ) )
219+ . ok_or ( SemanticError :: InvalidAmount )
220+ } ,
221+ Some ( Amount :: Currency { .. } ) => Err ( SemanticError :: UnsupportedCurrency ) ,
222+ None => Err ( SemanticError :: MissingAmount ) ,
223+ } ,
224+ }
225+ }
226+
227+ fn new (
228+ invreq_bytes : & ' a Vec < u8 > , contents : InvoiceContents , keys : Option < KeyPair >
229+ ) -> Result < Self , SemanticError > {
190230 if contents. fields ( ) . payment_paths . is_empty ( ) {
191231 return Err ( SemanticError :: MissingPaths ) ;
192232 }
193233
194- Ok ( Self { invreq_bytes, invoice : contents } )
234+ Ok ( Self {
235+ invreq_bytes,
236+ invoice : contents,
237+ keys,
238+ signing_pubkey_strategy : core:: marker:: PhantomData ,
239+ } )
195240 }
196241
197242 /// Sets the [`Invoice::relative_expiry`] as seconds since [`Invoice::created_at`]. Any expiry
@@ -248,7 +293,9 @@ impl<'a> InvoiceBuilder<'a> {
248293 self . invoice . fields_mut ( ) . features . set_basic_mpp_optional ( ) ;
249294 self
250295 }
296+ }
251297
298+ impl < ' a > InvoiceBuilder < ' a , ExplicitSigningPubkey > {
252299 /// Builds an unsigned [`Invoice`] after checking for valid semantics. It can be signed by
253300 /// [`UnsignedInvoice::sign`].
254301 pub fn build ( self ) -> Result < UnsignedInvoice < ' a > , SemanticError > {
@@ -258,11 +305,36 @@ impl<'a> InvoiceBuilder<'a> {
258305 }
259306 }
260307
261- let InvoiceBuilder { invreq_bytes, invoice } = self ;
308+ let InvoiceBuilder { invreq_bytes, invoice, .. } = self ;
262309 Ok ( UnsignedInvoice { invreq_bytes, invoice } )
263310 }
264311}
265312
313+ impl < ' a > InvoiceBuilder < ' a , DerivedSigningPubkey > {
314+ /// Builds a signed [`Invoice`] after checking for valid semantics.
315+ pub fn build_and_sign < T : secp256k1:: Signing > (
316+ self , secp_ctx : & Secp256k1 < T >
317+ ) -> Result < Invoice , SemanticError > {
318+ #[ cfg( feature = "std" ) ] {
319+ if self . invoice . is_offer_or_refund_expired ( ) {
320+ return Err ( SemanticError :: AlreadyExpired ) ;
321+ }
322+ }
323+
324+ let InvoiceBuilder { invreq_bytes, invoice, keys, .. } = self ;
325+ let keys = match & invoice {
326+ InvoiceContents :: ForOffer { .. } => keys. unwrap ( ) ,
327+ InvoiceContents :: ForRefund { .. } => unreachable ! ( ) ,
328+ } ;
329+
330+ let unsigned_invoice = UnsignedInvoice { invreq_bytes, invoice } ;
331+ let invoice = unsigned_invoice
332+ . sign :: < _ , Infallible > ( |digest| Ok ( secp_ctx. sign_schnorr_no_aux_rand ( digest, & keys) ) )
333+ . unwrap ( ) ;
334+ Ok ( invoice)
335+ }
336+ }
337+
266338/// A semantically valid [`Invoice`] that hasn't been signed.
267339pub struct UnsignedInvoice < ' a > {
268340 invreq_bytes : & ' a Vec < u8 > ,
@@ -551,7 +623,10 @@ impl InvoiceContents {
551623 } ,
552624 } ;
553625
554- signer:: verify_metadata ( metadata, key, iv_bytes, payer_id, tlv_stream, secp_ctx)
626+ match signer:: verify_metadata ( metadata, key, iv_bytes, payer_id, tlv_stream, secp_ctx) {
627+ Ok ( _) => true ,
628+ Err ( ( ) ) => false ,
629+ }
555630 }
556631
557632 fn derives_keys ( & self ) -> bool {
@@ -831,15 +906,18 @@ mod tests {
831906 use bitcoin:: util:: schnorr:: TweakedPublicKey ;
832907 use core:: convert:: TryFrom ;
833908 use core:: time:: Duration ;
834- use crate :: ln :: msgs :: DecodeError ;
909+ use crate :: chain :: keysinterface :: KeyMaterial ;
835910 use crate :: ln:: features:: Bolt12InvoiceFeatures ;
911+ use crate :: ln:: inbound_payment:: ExpandedKey ;
912+ use crate :: ln:: msgs:: DecodeError ;
836913 use crate :: offers:: invoice_request:: InvoiceRequestTlvStreamRef ;
837914 use crate :: offers:: merkle:: { SignError , SignatureTlvStreamRef , self } ;
838915 use crate :: offers:: offer:: { OfferBuilder , OfferTlvStreamRef , Quantity } ;
839916 use crate :: offers:: parse:: { ParseError , SemanticError } ;
840917 use crate :: offers:: payer:: PayerTlvStreamRef ;
841918 use crate :: offers:: refund:: RefundBuilder ;
842919 use crate :: offers:: test_utils:: * ;
920+ use crate :: onion_message:: { BlindedHop , BlindedPath } ;
843921 use crate :: util:: ser:: { BigSize , Iterable , Writeable } ;
844922
845923 trait ToBytes {
@@ -1084,6 +1162,67 @@ mod tests {
10841162 }
10851163 }
10861164
1165+ #[ test]
1166+ fn builds_invoice_from_offer_using_derived_keys ( ) {
1167+ let desc = "foo" . to_string ( ) ;
1168+ let node_id = recipient_pubkey ( ) ;
1169+ let expanded_key = ExpandedKey :: new ( & KeyMaterial ( [ 42 ; 32 ] ) ) ;
1170+ let entropy = FixedEntropy { } ;
1171+ let secp_ctx = Secp256k1 :: new ( ) ;
1172+
1173+ let blinded_path = BlindedPath {
1174+ introduction_node_id : pubkey ( 40 ) ,
1175+ blinding_point : pubkey ( 41 ) ,
1176+ blinded_hops : vec ! [
1177+ BlindedHop { blinded_node_id: pubkey( 42 ) , encrypted_payload: vec![ 0 ; 43 ] } ,
1178+ BlindedHop { blinded_node_id: node_id, encrypted_payload: vec![ 0 ; 44 ] } ,
1179+ ] ,
1180+ } ;
1181+
1182+ let offer = OfferBuilder
1183+ :: deriving_signing_pubkey ( desc, node_id, & expanded_key, & entropy, & secp_ctx)
1184+ . amount_msats ( 1000 )
1185+ . path ( blinded_path)
1186+ . build ( ) . unwrap ( ) ;
1187+ let invoice_request = offer. request_invoice ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) ) . unwrap ( )
1188+ . build ( ) . unwrap ( )
1189+ . sign ( payer_sign) . unwrap ( ) ;
1190+
1191+ if let Err ( e) = invoice_request
1192+ . verify_and_respond_using_derived_keys_no_std (
1193+ payment_paths ( ) , payment_hash ( ) , now ( ) , & expanded_key, & secp_ctx
1194+ )
1195+ . unwrap ( )
1196+ . build_and_sign ( & secp_ctx)
1197+ {
1198+ panic ! ( "error building invoice: {:?}" , e) ;
1199+ }
1200+
1201+ let expanded_key = ExpandedKey :: new ( & KeyMaterial ( [ 41 ; 32 ] ) ) ;
1202+ match invoice_request. verify_and_respond_using_derived_keys_no_std (
1203+ payment_paths ( ) , payment_hash ( ) , now ( ) , & expanded_key, & secp_ctx
1204+ ) {
1205+ Ok ( _) => panic ! ( "expected error" ) ,
1206+ Err ( e) => assert_eq ! ( e, SemanticError :: InvalidMetadata ) ,
1207+ }
1208+
1209+ let desc = "foo" . to_string ( ) ;
1210+ let offer = OfferBuilder
1211+ :: deriving_signing_pubkey ( desc, node_id, & expanded_key, & entropy, & secp_ctx)
1212+ . amount_msats ( 1000 )
1213+ . build ( ) . unwrap ( ) ;
1214+ let invoice_request = offer. request_invoice ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) ) . unwrap ( )
1215+ . build ( ) . unwrap ( )
1216+ . sign ( payer_sign) . unwrap ( ) ;
1217+
1218+ match invoice_request. verify_and_respond_using_derived_keys_no_std (
1219+ payment_paths ( ) , payment_hash ( ) , now ( ) , & expanded_key, & secp_ctx
1220+ ) {
1221+ Ok ( _) => panic ! ( "expected error" ) ,
1222+ Err ( e) => assert_eq ! ( e, SemanticError :: InvalidMetadata ) ,
1223+ }
1224+ }
1225+
10871226 #[ test]
10881227 fn builds_invoice_with_relative_expiry ( ) {
10891228 let now = now ( ) ;
0 commit comments