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 ) ,
@@ -568,9 +612,15 @@ impl Quantity {
568612 }
569613}
570614
571- tlv_stream ! ( OfferTlvStream , OfferTlvStreamRef , 1 ..80 , {
615+ /// Valid type range for offer TLV records.
616+ const OFFER_TYPES : core:: ops:: Range < u64 > = 1 ..80 ;
617+
618+ /// TLV record type for [`Offer::metadata`].
619+ const OFFER_METADATA_TYPE : u64 = 4 ;
620+
621+ tlv_stream ! ( OfferTlvStream , OfferTlvStreamRef , OFFER_TYPES , {
572622 ( 2 , chains: ( Vec <ChainHash >, WithoutLength ) ) ,
573- ( 4 , metadata: ( Vec <u8 >, WithoutLength ) ) ,
623+ ( OFFER_METADATA_TYPE , metadata: ( Vec <u8 >, WithoutLength ) ) ,
574624 ( 6 , currency: CurrencyCode ) ,
575625 ( 8 , amount: ( u64 , HighZeroBytesDroppedBigSize ) ) ,
576626 ( 10 , description: ( String , WithoutLength ) ) ,
@@ -664,17 +714,40 @@ mod tests {
664714
665715 use bitcoin:: blockdata:: constants:: ChainHash ;
666716 use bitcoin:: network:: constants:: Network ;
667- use bitcoin:: secp256k1:: { PublicKey , Secp256k1 , SecretKey } ;
668- use core:: convert:: TryFrom ;
717+ use bitcoin:: secp256k1:: { KeyPair , Message , PublicKey , Secp256k1 , SecretKey } ;
718+ use bitcoin:: secp256k1:: schnorr:: Signature ;
719+ use core:: convert:: { Infallible , TryFrom } ;
669720 use core:: num:: NonZeroU64 ;
670721 use core:: time:: Duration ;
722+ use crate :: chain:: keysinterface:: KeyMaterial ;
671723 use crate :: ln:: features:: OfferFeatures ;
724+ use crate :: ln:: inbound_payment:: ExpandedKey ;
672725 use crate :: ln:: msgs:: { DecodeError , MAX_VALUE_MSAT } ;
673726 use crate :: offers:: parse:: { ParseError , SemanticError } ;
674727 use crate :: onion_message:: { BlindedHop , BlindedPath } ;
675728 use crate :: util:: ser:: { BigSize , Writeable } ;
676729 use crate :: util:: string:: PrintableString ;
677730
731+ fn payer_keys ( ) -> KeyPair {
732+ let secp_ctx = Secp256k1 :: new ( ) ;
733+ KeyPair :: from_secret_key ( & secp_ctx, & SecretKey :: from_slice ( & [ 42 ; 32 ] ) . unwrap ( ) )
734+ }
735+
736+ fn payer_sign ( digest : & Message ) -> Result < Signature , Infallible > {
737+ let secp_ctx = Secp256k1 :: new ( ) ;
738+ let keys = KeyPair :: from_secret_key ( & secp_ctx, & SecretKey :: from_slice ( & [ 42 ; 32 ] ) . unwrap ( ) ) ;
739+ Ok ( secp_ctx. sign_schnorr_no_aux_rand ( digest, & keys) )
740+ }
741+
742+ fn payer_pubkey ( ) -> PublicKey {
743+ payer_keys ( ) . public_key ( )
744+ }
745+
746+ fn recipient_pubkey ( ) -> PublicKey {
747+ let secp_ctx = Secp256k1 :: new ( ) ;
748+ KeyPair :: from_secret_key ( & secp_ctx, & SecretKey :: from_slice ( & [ 43 ; 32 ] ) . unwrap ( ) ) . public_key ( )
749+ }
750+
678751 fn pubkey ( byte : u8 ) -> PublicKey {
679752 let secp_ctx = Secp256k1 :: new ( ) ;
680753 PublicKey :: from_secret_key ( & secp_ctx, & privkey ( byte) )
@@ -787,6 +860,35 @@ mod tests {
787860 assert_eq ! ( offer. as_tlv_stream( ) . metadata, Some ( & vec![ 43 ; 32 ] ) ) ;
788861 }
789862
863+ #[ test]
864+ fn builds_offer_with_metadata_derived ( ) {
865+ let keys = ExpandedKey :: new ( & KeyMaterial ( [ 42 ; 32 ] ) ) ;
866+ let invoice_request = OfferBuilder :: new ( "foo" . into ( ) , recipient_pubkey ( ) )
867+ . metadata_derived ( & keys)
868+ . amount_msats ( 1000 )
869+ . build ( ) . unwrap ( )
870+ . request_invoice ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) ) . unwrap ( )
871+ . build ( ) . unwrap ( )
872+ . sign ( payer_sign) . unwrap ( ) ;
873+ assert ! ( invoice_request. verify( & keys) ) ;
874+
875+ let offer = OfferBuilder :: new ( "foo" . into ( ) , recipient_pubkey ( ) )
876+ . metadata_derived ( & keys)
877+ . amount_msats ( 1000 )
878+ . build ( ) . unwrap ( ) ;
879+ let mut tlv_stream = offer. as_tlv_stream ( ) ;
880+ tlv_stream. amount = Some ( 100 ) ;
881+
882+ let mut encoded_offer = Vec :: new ( ) ;
883+ tlv_stream. write ( & mut encoded_offer) . unwrap ( ) ;
884+
885+ let invoice_request = Offer :: try_from ( encoded_offer) . unwrap ( )
886+ . request_invoice ( vec ! [ 1 ; 32 ] , payer_pubkey ( ) ) . unwrap ( )
887+ . build ( ) . unwrap ( )
888+ . sign ( payer_sign) . unwrap ( ) ;
889+ assert ! ( !invoice_request. verify( & keys) ) ;
890+ }
891+
790892 #[ test]
791893 fn builds_offer_with_amount ( ) {
792894 let bitcoin_amount = Amount :: Bitcoin { amount_msats : 1000 } ;
0 commit comments