@@ -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 }
@@ -747,7 +759,7 @@ impl core::fmt::Display for Offer {
747759
748760#[ cfg( test) ]
749761mod tests {
750- use super :: { Amount , Offer , OfferBuilder , OfferTlvStreamRef , Quantity } ;
762+ use super :: { Amount , Offer , OfferBuilder , OfferTlvStreamRef , Quantity , SigningPubkey } ;
751763
752764 use bitcoin:: blockdata:: constants:: ChainHash ;
753765 use bitcoin:: network:: constants:: Network ;
@@ -758,7 +770,7 @@ mod tests {
758770 use core:: time:: Duration ;
759771 use crate :: chain:: keysinterface:: KeyMaterial ;
760772 use crate :: ln:: features:: OfferFeatures ;
761- use crate :: ln:: inbound_payment:: ExpandedKey ;
773+ use crate :: ln:: inbound_payment:: { ExpandedKey , Nonce } ;
762774 use crate :: ln:: msgs:: { DecodeError , MAX_VALUE_MSAT } ;
763775 use crate :: offers:: parse:: { ParseError , SemanticError } ;
764776 use crate :: onion_message:: { BlindedHop , BlindedPath } ;
@@ -901,7 +913,7 @@ mod tests {
901913 fn builds_offer_with_metadata_derived ( ) {
902914 let keys = ExpandedKey :: new ( & KeyMaterial ( [ 42 ; 32 ] ) ) ;
903915 let invoice_request = OfferBuilder :: new ( "foo" . into ( ) , recipient_pubkey ( ) . into ( ) )
904- . metadata_derived ( & keys)
916+ . metadata_derived ( & keys, Nonce ( [ 42 ; Nonce :: LENGTH ] ) )
905917 . amount_msats ( 1000 )
906918 . build ( ) . unwrap ( )
907919 . request_invoice ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) ) . unwrap ( )
@@ -910,7 +922,7 @@ mod tests {
910922 assert ! ( invoice_request. verify( & keys) ) ;
911923
912924 let offer = OfferBuilder :: new ( "foo" . into ( ) , recipient_pubkey ( ) . into ( ) )
913- . metadata_derived ( & keys)
925+ . metadata_derived ( & keys, Nonce ( [ 42 ; Nonce :: LENGTH ] ) )
914926 . amount_msats ( 1000 )
915927 . build ( ) . unwrap ( ) ;
916928 let mut tlv_stream = offer. as_tlv_stream ( ) ;
@@ -926,6 +938,36 @@ mod tests {
926938 assert ! ( !invoice_request. verify( & keys) ) ;
927939 }
928940
941+ #[ test]
942+ fn builds_offer_with_signing_pubkey_derived ( ) {
943+ let keys = ExpandedKey :: new ( & KeyMaterial ( [ 42 ; 32 ] ) ) ;
944+ let nonce = Nonce ( [ 42 ; Nonce :: LENGTH ] ) ;
945+
946+ let recipient_pubkey = SigningPubkey :: Derived ( & keys, nonce) ;
947+ let offer = OfferBuilder :: new ( "foo" . into ( ) , recipient_pubkey)
948+ . amount_msats ( 1000 )
949+ . build ( ) . unwrap ( ) ;
950+ assert_eq ! ( offer. metadata( ) . unwrap( ) [ ..Nonce :: LENGTH ] , nonce. 0 ) ;
951+ assert_eq ! ( offer. signing_pubkey( ) , keys. signing_pubkey_for_offer( nonce) ) ;
952+
953+ let invoice_request = offer. request_invoice ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) ) . unwrap ( )
954+ . build ( ) . unwrap ( )
955+ . sign ( payer_sign) . unwrap ( ) ;
956+ assert ! ( invoice_request. verify( & keys) ) ;
957+
958+ let mut tlv_stream = offer. as_tlv_stream ( ) ;
959+ tlv_stream. amount = Some ( 100 ) ;
960+
961+ let mut encoded_offer = Vec :: new ( ) ;
962+ tlv_stream. write ( & mut encoded_offer) . unwrap ( ) ;
963+
964+ let invoice_request = Offer :: try_from ( encoded_offer) . unwrap ( )
965+ . request_invoice ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) ) . unwrap ( )
966+ . build ( ) . unwrap ( )
967+ . sign ( payer_sign) . unwrap ( ) ;
968+ assert ! ( !invoice_request. verify( & keys) ) ;
969+ }
970+
929971 #[ test]
930972 fn builds_offer_with_amount ( ) {
931973 let bitcoin_amount = Amount :: Bitcoin { amount_msats : 1000 } ;
0 commit comments