6767//! ```
6868
6969use bitcoin:: blockdata:: constants:: ChainHash ;
70+ use bitcoin:: hashes:: { Hash , HashEngine } ;
71+ use bitcoin:: hashes:: hmac:: { Hmac , HmacEngine } ;
72+ use bitcoin:: hashes:: sha256:: Hash as Sha256 ;
7073use bitcoin:: network:: constants:: Network ;
7174use bitcoin:: secp256k1:: PublicKey ;
7275use core:: convert:: TryFrom ;
@@ -75,8 +78,10 @@ use core::str::FromStr;
7578use core:: time:: Duration ;
7679use crate :: io;
7780use crate :: ln:: features:: OfferFeatures ;
81+ use crate :: ln:: inbound_payment:: ExpandedKey ;
7882use crate :: ln:: msgs:: MAX_VALUE_MSAT ;
7983use crate :: offers:: invoice_request:: InvoiceRequestBuilder ;
84+ use crate :: offers:: merkle:: TlvStream ;
8085use crate :: offers:: parse:: { Bech32Encode , ParseError , ParsedMessage , SemanticError } ;
8186use crate :: onion_message:: BlindedPath ;
8287use crate :: util:: ser:: { HighZeroBytesDroppedBigSize , WithoutLength , Writeable , Writer } ;
@@ -94,6 +99,7 @@ use std::time::SystemTime;
9499/// [module-level documentation]: self
95100pub struct OfferBuilder {
96101 offer : OfferContents ,
102+ hmac : Option < HmacEngine < Sha256 > > ,
97103}
98104
99105impl OfferBuilder {
@@ -108,7 +114,7 @@ impl OfferBuilder {
108114 features : OfferFeatures :: empty ( ) , absolute_expiry : None , issuer : None , paths : None ,
109115 supported_quantity : Quantity :: One , signing_pubkey,
110116 } ;
111- OfferBuilder { offer }
117+ OfferBuilder { offer, hmac : None }
112118 }
113119
114120 /// Adds the chain hash of the given [`Network`] to [`Offer::chains`]. If not called,
@@ -127,11 +133,24 @@ impl OfferBuilder {
127133 self
128134 }
129135
130- /// Sets the [`Offer::metadata`].
136+ /// Sets the [`Offer::metadata`] to the given bytes .
131137 ///
132- /// Successive calls to this method will override the previous setting.
138+ /// Successive calls to this method will override the previous setting and any previous calls to
139+ /// [`OfferBuilder::metadata_derived`].
133140 pub fn metadata ( mut self , metadata : Vec < u8 > ) -> Self {
134141 self . offer . metadata = Some ( metadata) ;
142+ self . hmac = None ;
143+ self
144+ }
145+
146+ /// Sets the [`Offer::metadata`] derived from the given `key` and any fields set prior to
147+ /// calling [`OfferBuilder::build`].
148+ ///
149+ /// Successive calls to this method will override the previous setting and any previous calls to
150+ /// [`OfferBuilder::metadata`].
151+ pub fn metadata_derived ( mut self , key : & ExpandedKey ) -> Self {
152+ self . offer . metadata = None ;
153+ self . hmac = Some ( key. hmac_for_offer ( ) ) ;
135154 self
136155 }
137156
@@ -204,6 +223,11 @@ impl OfferBuilder {
204223 }
205224 }
206225
226+ if let Some ( mut hmac) = self . hmac {
227+ self . offer . write ( & mut hmac) . unwrap ( ) ;
228+ self . offer . metadata = Some ( Hmac :: from_engine ( hmac) . into_inner ( ) . to_vec ( ) ) ;
229+ }
230+
207231 let mut bytes = Vec :: new ( ) ;
208232 self . offer . write ( & mut bytes) . unwrap ( ) ;
209233
@@ -482,6 +506,26 @@ impl OfferContents {
482506 self . signing_pubkey
483507 }
484508
509+ /// Verifies that the offer metadata was produced from the offer in the TLV stream.
510+ pub ( super ) fn verify ( & self , tlv_stream : TlvStream < ' _ > , key : & ExpandedKey ) -> bool {
511+ match & self . metadata {
512+ Some ( metadata) => {
513+ let mut hmac = key. hmac_for_offer ( ) ;
514+
515+ for record in tlv_stream. range ( OFFER_TYPES ) {
516+ if record. r#type != OFFER_METADATA_TYPE {
517+ hmac. input ( record. record_bytes ) ;
518+ } else {
519+ // TODO: Assert value bytes == metadata?
520+ }
521+ }
522+
523+ metadata == & Hmac :: from_engine ( hmac) . into_inner ( )
524+ } ,
525+ None => false ,
526+ }
527+ }
528+
485529 pub ( super ) fn as_tlv_stream ( & self ) -> OfferTlvStreamRef {
486530 let ( currency, amount) = match & self . amount {
487531 None => ( None , None ) ,
@@ -569,9 +613,15 @@ impl Quantity {
569613 }
570614}
571615
572- tlv_stream ! ( OfferTlvStream , OfferTlvStreamRef , 1 ..80 , {
616+ /// Valid type range for offer TLV records.
617+ const OFFER_TYPES : core:: ops:: Range < u64 > = 1 ..80 ;
618+
619+ /// TLV record type for [`Offer::metadata`].
620+ const OFFER_METADATA_TYPE : u64 = 4 ;
621+
622+ tlv_stream ! ( OfferTlvStream , OfferTlvStreamRef , OFFER_TYPES , {
573623 ( 2 , chains: ( Vec <ChainHash >, WithoutLength ) ) ,
574- ( 4 , metadata: ( Vec <u8 >, WithoutLength ) ) ,
624+ ( OFFER_METADATA_TYPE , metadata: ( Vec <u8 >, WithoutLength ) ) ,
575625 ( 6 , currency: CurrencyCode ) ,
576626 ( 8 , amount: ( u64 , HighZeroBytesDroppedBigSize ) ) ,
577627 ( 10 , description: ( String , WithoutLength ) ) ,
@@ -665,17 +715,40 @@ mod tests {
665715
666716 use bitcoin:: blockdata:: constants:: ChainHash ;
667717 use bitcoin:: network:: constants:: Network ;
668- use bitcoin:: secp256k1:: { PublicKey , Secp256k1 , SecretKey } ;
669- use core:: convert:: TryFrom ;
718+ use bitcoin:: secp256k1:: { KeyPair , Message , PublicKey , Secp256k1 , SecretKey } ;
719+ use bitcoin:: secp256k1:: schnorr:: Signature ;
720+ use core:: convert:: { Infallible , TryFrom } ;
670721 use core:: num:: NonZeroU64 ;
671722 use core:: time:: Duration ;
723+ use crate :: chain:: keysinterface:: KeyMaterial ;
672724 use crate :: ln:: features:: OfferFeatures ;
725+ use crate :: ln:: inbound_payment:: ExpandedKey ;
673726 use crate :: ln:: msgs:: { DecodeError , MAX_VALUE_MSAT } ;
674727 use crate :: offers:: parse:: { ParseError , SemanticError } ;
675728 use crate :: onion_message:: { BlindedHop , BlindedPath } ;
676729 use crate :: util:: ser:: { BigSize , Writeable } ;
677730 use crate :: util:: string:: PrintableString ;
678731
732+ fn payer_keys ( ) -> KeyPair {
733+ let secp_ctx = Secp256k1 :: new ( ) ;
734+ KeyPair :: from_secret_key ( & secp_ctx, & SecretKey :: from_slice ( & [ 42 ; 32 ] ) . unwrap ( ) )
735+ }
736+
737+ fn payer_sign ( digest : & Message ) -> Result < Signature , Infallible > {
738+ let secp_ctx = Secp256k1 :: new ( ) ;
739+ let keys = KeyPair :: from_secret_key ( & secp_ctx, & SecretKey :: from_slice ( & [ 42 ; 32 ] ) . unwrap ( ) ) ;
740+ Ok ( secp_ctx. sign_schnorr_no_aux_rand ( digest, & keys) )
741+ }
742+
743+ fn payer_pubkey ( ) -> PublicKey {
744+ payer_keys ( ) . public_key ( )
745+ }
746+
747+ fn recipient_pubkey ( ) -> PublicKey {
748+ let secp_ctx = Secp256k1 :: new ( ) ;
749+ KeyPair :: from_secret_key ( & secp_ctx, & SecretKey :: from_slice ( & [ 43 ; 32 ] ) . unwrap ( ) ) . public_key ( )
750+ }
751+
679752 fn pubkey ( byte : u8 ) -> PublicKey {
680753 let secp_ctx = Secp256k1 :: new ( ) ;
681754 PublicKey :: from_secret_key ( & secp_ctx, & privkey ( byte) )
@@ -788,6 +861,35 @@ mod tests {
788861 assert_eq ! ( offer. as_tlv_stream( ) . metadata, Some ( & vec![ 43 ; 32 ] ) ) ;
789862 }
790863
864+ #[ test]
865+ fn builds_offer_with_metadata_derived ( ) {
866+ let keys = ExpandedKey :: new ( & KeyMaterial ( [ 42 ; 32 ] ) ) ;
867+ let invoice_request = OfferBuilder :: new ( "foo" . into ( ) , recipient_pubkey ( ) )
868+ . metadata_derived ( & keys)
869+ . amount_msats ( 1000 )
870+ . build ( ) . unwrap ( )
871+ . request_invoice ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) ) . unwrap ( )
872+ . build ( ) . unwrap ( )
873+ . sign ( payer_sign) . unwrap ( ) ;
874+ assert ! ( invoice_request. verify( & keys) ) ;
875+
876+ let offer = OfferBuilder :: new ( "foo" . into ( ) , recipient_pubkey ( ) )
877+ . metadata_derived ( & keys)
878+ . amount_msats ( 1000 )
879+ . build ( ) . unwrap ( ) ;
880+ let mut tlv_stream = offer. as_tlv_stream ( ) ;
881+ tlv_stream. amount = Some ( 100 ) ;
882+
883+ let mut encoded_offer = Vec :: new ( ) ;
884+ tlv_stream. write ( & mut encoded_offer) . unwrap ( ) ;
885+
886+ let invoice_request = Offer :: try_from ( encoded_offer) . unwrap ( )
887+ . request_invoice ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) ) . unwrap ( )
888+ . build ( ) . unwrap ( )
889+ . sign ( payer_sign) . unwrap ( ) ;
890+ assert ! ( !invoice_request. verify( & keys) ) ;
891+ }
892+
791893 #[ test]
792894 fn builds_offer_with_amount ( ) {
793895 let bitcoin_amount = Amount :: Bitcoin { amount_msats : 1000 } ;
0 commit comments