@@ -72,13 +72,13 @@ use bitcoin::hashes::hmac::{Hmac, HmacEngine};
7272use bitcoin:: hashes:: sha256:: Hash as Sha256 ;
7373use bitcoin:: network:: constants:: Network ;
7474use bitcoin:: secp256k1:: PublicKey ;
75- use core:: convert:: TryFrom ;
75+ use core:: convert:: { TryFrom , TryInto } ;
7676use core:: num:: NonZeroU64 ;
7777use core:: str:: FromStr ;
7878use core:: time:: Duration ;
7979use crate :: io;
8080use crate :: ln:: features:: OfferFeatures ;
81- use crate :: ln:: inbound_payment:: ExpandedKey ;
81+ use crate :: ln:: inbound_payment:: { ExpandedKey , Nonce } ;
8282use crate :: ln:: msgs:: MAX_VALUE_MSAT ;
8383use crate :: offers:: invoice_request:: InvoiceRequestBuilder ;
8484use crate :: offers:: merkle:: TlvStream ;
@@ -99,16 +99,16 @@ use std::time::SystemTime;
9999/// [module-level documentation]: self
100100pub struct OfferBuilder {
101101 offer : OfferContents ,
102- hmac : Option < HmacEngine < Sha256 > > ,
102+ metadata_material : Option < ( Nonce , HmacEngine < Sha256 > ) > ,
103103}
104104
105105///
106- pub enum SigningPubkey {
106+ pub enum SigningPubkey < ' a > {
107107 Explicit ( PublicKey ) ,
108- Derived ,
108+ Derived ( & ' a ExpandedKey , Nonce ) ,
109109}
110110
111- impl From < PublicKey > for SigningPubkey {
111+ impl < ' a > From < PublicKey > for SigningPubkey < ' a > {
112112 fn from ( pubkey : PublicKey ) -> Self {
113113 SigningPubkey :: Explicit ( pubkey)
114114 }
@@ -121,16 +121,20 @@ impl OfferBuilder {
121121 ///
122122 /// Use a different pubkey per offer to avoid correlating offers.
123123 pub fn new ( description : String , signing_pubkey : SigningPubkey ) -> Self {
124- let signing_pubkey = match signing_pubkey {
125- SigningPubkey :: Explicit ( pubkey) => Some ( pubkey) ,
126- SigningPubkey :: Derived => None ,
124+ let ( metadata_material, signing_pubkey) = match signing_pubkey {
125+ SigningPubkey :: Explicit ( pubkey) => ( None , Some ( pubkey) ) ,
126+ SigningPubkey :: Derived ( expanded_key, nonce) => {
127+ let metadata_material = ( nonce, expanded_key. hmac_for_offer ( nonce) ) ;
128+ let signing_pubkey = expanded_key. signing_pubkey_for_offer ( nonce) ;
129+ ( Some ( metadata_material) , Some ( signing_pubkey) )
130+ } ,
127131 } ;
128132 let offer = OfferContents {
129133 chains : None , metadata : None , amount : None , description,
130134 features : OfferFeatures :: empty ( ) , absolute_expiry : None , issuer : None , paths : None ,
131135 supported_quantity : Quantity :: One , signing_pubkey,
132136 } ;
133- OfferBuilder { offer, hmac : None }
137+ OfferBuilder { offer, metadata_material }
134138 }
135139
136140 /// Adds the chain hash of the given [`Network`] to [`Offer::chains`]. If not called,
@@ -159,21 +163,21 @@ impl OfferBuilder {
159163 }
160164
161165 self . offer . metadata = Some ( metadata) ;
162- self . hmac = None ;
166+ self . metadata_material = None ;
163167 Ok ( self )
164168 }
165169
166170 /// Sets the [`Offer::metadata`] derived from the given `key` and any fields set prior to
167171 /// calling [`OfferBuilder::build`]. Allows for stateless verification of an [`InvoiceRequest`].
168172 ///
169173 /// Successive calls to this method will override the previous setting and any previous calls to
170- /// [`OfferBuilder::metadata`]. Must be called if the builder was constructed with
171- /// [`SigningPubkey::Derived`] in order to derive [`Offer::signing_pubkey`] .
174+ /// [`OfferBuilder::metadata`]. Does not need to be called if the builder was constructed with
175+ /// [`SigningPubkey::Derived`].
172176 ///
173177 /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
174- pub fn metadata_derived ( mut self , key : & ExpandedKey ) -> Self {
178+ pub fn metadata_derived ( mut self , key : & ExpandedKey , nonce : Nonce ) -> Self {
175179 self . offer . metadata = None ;
176- self . hmac = Some ( key. hmac_for_offer ( ) ) ;
180+ self . metadata_material = Some ( ( nonce , key. hmac_for_offer ( nonce ) ) ) ;
177181 self
178182 }
179183
@@ -246,14 +250,17 @@ impl OfferBuilder {
246250 }
247251 }
248252
249- // Created the metadata for stateless verification and derive the signing_pubkey, if needed .
250- if let Some ( mut hmac) = self . hmac {
253+ // Created the metadata for stateless verification.
254+ if let Some ( ( nonce , mut hmac) ) = self . metadata_material {
251255 debug_assert ! ( self . offer. metadata. is_none( ) ) ;
252256 let mut tlv_stream = self . offer . as_tlv_stream ( ) ;
253257 tlv_stream. node_id = None ;
254258 tlv_stream. write ( & mut hmac) . unwrap ( ) ;
255259
256- self . offer . metadata = Some ( Hmac :: from_engine ( hmac) . into_inner ( ) . to_vec ( ) ) ;
260+ let mut metadata = nonce. as_slice ( ) . to_vec ( ) ;
261+ metadata. extend_from_slice ( & Hmac :: from_engine ( hmac) . into_inner ( ) ) ;
262+
263+ self . offer . metadata = Some ( metadata) ;
257264 }
258265
259266 if self . offer . signing_pubkey . is_none ( ) {
@@ -542,7 +549,12 @@ impl OfferContents {
542549 pub ( super ) fn verify ( & self , tlv_stream : TlvStream < ' _ > , key : & ExpandedKey ) -> bool {
543550 match & self . metadata {
544551 Some ( metadata) => {
545- let mut hmac = key. hmac_for_offer ( ) ;
552+ let mut hmac = if metadata. len ( ) < Nonce :: LENGTH {
553+ return false ;
554+ } else {
555+ let nonce = Nonce ( metadata[ ..Nonce :: LENGTH ] . try_into ( ) . unwrap ( ) ) ;
556+ key. hmac_for_offer ( nonce)
557+ } ;
546558
547559 for record in tlv_stream. range ( OFFER_TYPES ) {
548560 match record. r#type {
@@ -553,7 +565,7 @@ impl OfferContents {
553565 }
554566 }
555567
556- metadata == & Hmac :: from_engine ( hmac) . into_inner ( )
568+ & metadata[ Nonce :: LENGTH .. ] == & Hmac :: from_engine ( hmac) . into_inner ( )
557569 } ,
558570 None => false ,
559571 }
@@ -746,7 +758,7 @@ impl core::fmt::Display for Offer {
746758
747759#[ cfg( test) ]
748760mod tests {
749- use super :: { Amount , Offer , OfferBuilder , OfferTlvStreamRef , Quantity } ;
761+ use super :: { Amount , Offer , OfferBuilder , OfferTlvStreamRef , Quantity , SigningPubkey } ;
750762
751763 use bitcoin:: blockdata:: constants:: ChainHash ;
752764 use bitcoin:: network:: constants:: Network ;
@@ -757,7 +769,7 @@ mod tests {
757769 use core:: time:: Duration ;
758770 use crate :: chain:: keysinterface:: KeyMaterial ;
759771 use crate :: ln:: features:: OfferFeatures ;
760- use crate :: ln:: inbound_payment:: ExpandedKey ;
772+ use crate :: ln:: inbound_payment:: { ExpandedKey , Nonce } ;
761773 use crate :: ln:: msgs:: { DecodeError , MAX_VALUE_MSAT } ;
762774 use crate :: offers:: parse:: { ParseError , SemanticError } ;
763775 use crate :: onion_message:: { BlindedHop , BlindedPath } ;
@@ -900,7 +912,7 @@ mod tests {
900912 fn builds_offer_with_metadata_derived ( ) {
901913 let keys = ExpandedKey :: new ( & KeyMaterial ( [ 42 ; 32 ] ) ) ;
902914 let invoice_request = OfferBuilder :: new ( "foo" . into ( ) , recipient_pubkey ( ) . into ( ) )
903- . metadata_derived ( & keys)
915+ . metadata_derived ( & keys, Nonce ( [ 42 ; Nonce :: LENGTH ] ) )
904916 . amount_msats ( 1000 )
905917 . build ( ) . unwrap ( )
906918 . request_invoice ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) ) . unwrap ( )
@@ -909,7 +921,7 @@ mod tests {
909921 assert ! ( invoice_request. verify( & keys) ) ;
910922
911923 let offer = OfferBuilder :: new ( "foo" . into ( ) , recipient_pubkey ( ) . into ( ) )
912- . metadata_derived ( & keys)
924+ . metadata_derived ( & keys, Nonce ( [ 42 ; Nonce :: LENGTH ] ) )
913925 . amount_msats ( 1000 )
914926 . build ( ) . unwrap ( ) ;
915927 let mut tlv_stream = offer. as_tlv_stream ( ) ;
@@ -925,6 +937,36 @@ mod tests {
925937 assert ! ( !invoice_request. verify( & keys) ) ;
926938 }
927939
940+ #[ test]
941+ fn builds_offer_with_signing_pubkey_derived ( ) {
942+ let keys = ExpandedKey :: new ( & KeyMaterial ( [ 42 ; 32 ] ) ) ;
943+ let nonce = Nonce ( [ 42 ; Nonce :: LENGTH ] ) ;
944+
945+ let recipient_pubkey = SigningPubkey :: Derived ( & keys, nonce) ;
946+ let offer = OfferBuilder :: new ( "foo" . into ( ) , recipient_pubkey)
947+ . amount_msats ( 1000 )
948+ . build ( ) . unwrap ( ) ;
949+ assert_eq ! ( offer. metadata( ) . unwrap( ) [ ..Nonce :: LENGTH ] , nonce. 0 ) ;
950+ assert_eq ! ( offer. signing_pubkey( ) , keys. signing_pubkey_for_offer( nonce) ) ;
951+
952+ let invoice_request = offer. request_invoice ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) ) . unwrap ( )
953+ . build ( ) . unwrap ( )
954+ . sign ( payer_sign) . unwrap ( ) ;
955+ assert ! ( invoice_request. verify( & keys) ) ;
956+
957+ let mut tlv_stream = offer. as_tlv_stream ( ) ;
958+ tlv_stream. amount = Some ( 100 ) ;
959+
960+ let mut encoded_offer = Vec :: new ( ) ;
961+ tlv_stream. write ( & mut encoded_offer) . unwrap ( ) ;
962+
963+ let invoice_request = Offer :: try_from ( encoded_offer) . unwrap ( )
964+ . request_invoice ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) ) . unwrap ( )
965+ . build ( ) . unwrap ( )
966+ . sign ( payer_sign) . unwrap ( ) ;
967+ assert ! ( !invoice_request. verify( & keys) ) ;
968+ }
969+
928970 #[ test]
929971 fn builds_offer_with_amount ( ) {
930972 let bitcoin_amount = Amount :: Bitcoin { amount_msats : 1000 } ;
0 commit comments