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 ) ,
@@ -565,9 +609,15 @@ impl Quantity {
565609 }
566610}
567611
568- tlv_stream ! ( OfferTlvStream , OfferTlvStreamRef , 1 ..80 , {
612+ /// Valid type range for offer TLV records.
613+ const OFFER_TYPES : core:: ops:: Range < u64 > = 1 ..80 ;
614+
615+ /// TLV record type for [`Offer::metadata`].
616+ const OFFER_METADATA_TYPE : u64 = 4 ;
617+
618+ tlv_stream ! ( OfferTlvStream , OfferTlvStreamRef , OFFER_TYPES , {
569619 ( 2 , chains: ( Vec <ChainHash >, WithoutLength ) ) ,
570- ( 4 , metadata: ( Vec <u8 >, WithoutLength ) ) ,
620+ ( OFFER_METADATA_TYPE , metadata: ( Vec <u8 >, WithoutLength ) ) ,
571621 ( 6 , currency: CurrencyCode ) ,
572622 ( 8 , amount: ( u64 , HighZeroBytesDroppedBigSize ) ) ,
573623 ( 10 , description: ( String , WithoutLength ) ) ,
@@ -661,17 +711,40 @@ mod tests {
661711
662712 use bitcoin:: blockdata:: constants:: ChainHash ;
663713 use bitcoin:: network:: constants:: Network ;
664- use bitcoin:: secp256k1:: { PublicKey , Secp256k1 , SecretKey } ;
665- use core:: convert:: TryFrom ;
714+ use bitcoin:: secp256k1:: { KeyPair , Message , PublicKey , Secp256k1 , SecretKey } ;
715+ use bitcoin:: secp256k1:: schnorr:: Signature ;
716+ use core:: convert:: { Infallible , TryFrom } ;
666717 use core:: num:: NonZeroU64 ;
667718 use core:: time:: Duration ;
719+ use crate :: chain:: keysinterface:: KeyMaterial ;
668720 use crate :: ln:: features:: OfferFeatures ;
721+ use crate :: ln:: inbound_payment:: ExpandedKey ;
669722 use crate :: ln:: msgs:: { DecodeError , MAX_VALUE_MSAT } ;
670723 use crate :: offers:: parse:: { ParseError , SemanticError } ;
671724 use crate :: onion_message:: { BlindedHop , BlindedPath } ;
672725 use crate :: util:: ser:: { BigSize , Writeable } ;
673726 use crate :: util:: string:: PrintableString ;
674727
728+ fn payer_keys ( ) -> KeyPair {
729+ let secp_ctx = Secp256k1 :: new ( ) ;
730+ KeyPair :: from_secret_key ( & secp_ctx, & SecretKey :: from_slice ( & [ 42 ; 32 ] ) . unwrap ( ) )
731+ }
732+
733+ fn payer_sign ( digest : & Message ) -> Result < Signature , Infallible > {
734+ let secp_ctx = Secp256k1 :: new ( ) ;
735+ let keys = KeyPair :: from_secret_key ( & secp_ctx, & SecretKey :: from_slice ( & [ 42 ; 32 ] ) . unwrap ( ) ) ;
736+ Ok ( secp_ctx. sign_schnorr_no_aux_rand ( digest, & keys) )
737+ }
738+
739+ fn payer_pubkey ( ) -> PublicKey {
740+ payer_keys ( ) . public_key ( )
741+ }
742+
743+ fn recipient_pubkey ( ) -> PublicKey {
744+ let secp_ctx = Secp256k1 :: new ( ) ;
745+ KeyPair :: from_secret_key ( & secp_ctx, & SecretKey :: from_slice ( & [ 43 ; 32 ] ) . unwrap ( ) ) . public_key ( )
746+ }
747+
675748 fn pubkey ( byte : u8 ) -> PublicKey {
676749 let secp_ctx = Secp256k1 :: new ( ) ;
677750 PublicKey :: from_secret_key ( & secp_ctx, & privkey ( byte) )
@@ -784,6 +857,35 @@ mod tests {
784857 assert_eq ! ( offer. as_tlv_stream( ) . metadata, Some ( & vec![ 43 ; 32 ] ) ) ;
785858 }
786859
860+ #[ test]
861+ fn builds_offer_with_metadata_derived ( ) {
862+ let keys = ExpandedKey :: new ( & KeyMaterial ( [ 42 ; 32 ] ) ) ;
863+ let invoice_request = OfferBuilder :: new ( "foo" . into ( ) , recipient_pubkey ( ) )
864+ . metadata_derived ( & keys)
865+ . amount_msats ( 1000 )
866+ . build ( ) . unwrap ( )
867+ . request_invoice ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) ) . unwrap ( )
868+ . build ( ) . unwrap ( )
869+ . sign ( payer_sign) . unwrap ( ) ;
870+ assert ! ( invoice_request. verify( & keys) ) ;
871+
872+ let offer = OfferBuilder :: new ( "foo" . into ( ) , recipient_pubkey ( ) )
873+ . metadata_derived ( & keys)
874+ . amount_msats ( 1000 )
875+ . build ( ) . unwrap ( ) ;
876+ let mut tlv_stream = offer. as_tlv_stream ( ) ;
877+ tlv_stream. amount = Some ( 100 ) ;
878+
879+ let mut encoded_offer = Vec :: new ( ) ;
880+ tlv_stream. write ( & mut encoded_offer) . unwrap ( ) ;
881+
882+ let invoice_request = Offer :: try_from ( encoded_offer) . unwrap ( )
883+ . request_invoice ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) ) . unwrap ( )
884+ . build ( ) . unwrap ( )
885+ . sign ( payer_sign) . unwrap ( ) ;
886+ assert ! ( !invoice_request. verify( & keys) ) ;
887+ }
888+
787889 #[ test]
788890 fn builds_offer_with_amount ( ) {
789891 let bitcoin_amount = Amount :: Bitcoin { amount_msats : 1000 } ;
0 commit comments