@@ -168,7 +168,7 @@ pub fn check_platform() {
168168///
169169/// (C-not exported) as we likely need to manually select one set of boolean type parameters.
170170#[ derive( Eq , PartialEq , Debug , Clone ) ]
171- pub struct InvoiceBuilder < D : tb:: Bool , H : tb:: Bool , T : tb:: Bool , C : tb:: Bool > {
171+ pub struct InvoiceBuilder < D : tb:: Bool , H : tb:: Bool , T : tb:: Bool , C : tb:: Bool , S : tb :: Bool > {
172172 currency : Currency ,
173173 amount : Option < u64 > ,
174174 si_prefix : Option < SiPrefix > ,
@@ -180,6 +180,7 @@ pub struct InvoiceBuilder<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool> {
180180 phantom_h : std:: marker:: PhantomData < H > ,
181181 phantom_t : std:: marker:: PhantomData < T > ,
182182 phantom_c : std:: marker:: PhantomData < C > ,
183+ phantom_s : std:: marker:: PhantomData < S > ,
183184}
184185
185186/// Represents a syntactically and semantically correct lightning BOLT11 invoice.
@@ -432,7 +433,7 @@ pub mod constants {
432433 pub const TAG_FEATURES : u8 = 5 ;
433434}
434435
435- impl InvoiceBuilder < tb:: False , tb:: False , tb:: False , tb:: False > {
436+ impl InvoiceBuilder < tb:: False , tb:: False , tb:: False , tb:: False , tb :: False > {
436437 /// Construct new, empty `InvoiceBuilder`. All necessary fields have to be filled first before
437438 /// `InvoiceBuilder::build(self)` becomes available.
438439 pub fn new ( currrency : Currency ) -> Self {
@@ -448,14 +449,15 @@ impl InvoiceBuilder<tb::False, tb::False, tb::False, tb::False> {
448449 phantom_h : std:: marker:: PhantomData ,
449450 phantom_t : std:: marker:: PhantomData ,
450451 phantom_c : std:: marker:: PhantomData ,
452+ phantom_s : std:: marker:: PhantomData ,
451453 }
452454 }
453455}
454456
455- impl < D : tb:: Bool , H : tb:: Bool , T : tb:: Bool , C : tb:: Bool > InvoiceBuilder < D , H , T , C > {
457+ impl < D : tb:: Bool , H : tb:: Bool , T : tb:: Bool , C : tb:: Bool , S : tb :: Bool > InvoiceBuilder < D , H , T , C , S > {
456458 /// Helper function to set the completeness flags.
457- fn set_flags < DN : tb:: Bool , HN : tb:: Bool , TN : tb:: Bool , CN : tb:: Bool > ( self ) -> InvoiceBuilder < DN , HN , TN , CN > {
458- InvoiceBuilder :: < DN , HN , TN , CN > {
459+ fn set_flags < DN : tb:: Bool , HN : tb:: Bool , TN : tb:: Bool , CN : tb:: Bool , SN : tb :: Bool > ( self ) -> InvoiceBuilder < DN , HN , TN , CN , SN > {
460+ InvoiceBuilder :: < DN , HN , TN , CN , SN > {
459461 currency : self . currency ,
460462 amount : self . amount ,
461463 si_prefix : self . si_prefix ,
@@ -467,6 +469,7 @@ impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool> InvoiceBuilder<D, H, T,
467469 phantom_h : std:: marker:: PhantomData ,
468470 phantom_t : std:: marker:: PhantomData ,
469471 phantom_c : std:: marker:: PhantomData ,
472+ phantom_s : std:: marker:: PhantomData ,
470473 }
471474 }
472475
@@ -487,12 +490,6 @@ impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool> InvoiceBuilder<D, H, T,
487490 self
488491 }
489492
490- /// Sets the payment secret
491- pub fn payment_secret ( mut self , payment_secret : PaymentSecret ) -> Self {
492- self . tagged_fields . push ( TaggedField :: PaymentSecret ( payment_secret) ) ;
493- self
494- }
495-
496493 /// Sets the expiry time
497494 pub fn expiry_time ( mut self , expiry_time : Duration ) -> Self {
498495 match ExpiryTime :: from_duration ( expiry_time) {
@@ -516,16 +513,9 @@ impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool> InvoiceBuilder<D, H, T,
516513 }
517514 self
518515 }
519-
520- /// Adds a features field which indicates the set of supported protocol extensions which the
521- /// origin node supports.
522- pub fn features ( mut self , features : InvoiceFeatures ) -> Self {
523- self . tagged_fields . push ( TaggedField :: Features ( features) ) ;
524- self
525- }
526516}
527517
528- impl < D : tb:: Bool , H : tb:: Bool , C : tb:: Bool > InvoiceBuilder < D , H , tb:: True , C > {
518+ impl < D : tb:: Bool , H : tb:: Bool , C : tb:: Bool , S : tb :: Bool > InvoiceBuilder < D , H , tb:: True , C , S > {
529519 /// Builds a `RawInvoice` if no `CreationError` occurred while construction any of the fields.
530520 pub fn build_raw ( self ) -> Result < RawInvoice , CreationError > {
531521
@@ -558,9 +548,9 @@ impl<D: tb::Bool, H: tb::Bool, C: tb::Bool> InvoiceBuilder<D, H, tb::True, C> {
558548 }
559549}
560550
561- impl < H : tb:: Bool , T : tb:: Bool , C : tb:: Bool > InvoiceBuilder < tb:: False , H , T , C > {
551+ impl < H : tb:: Bool , T : tb:: Bool , C : tb:: Bool , S : tb :: Bool > InvoiceBuilder < tb:: False , H , T , C , S > {
562552 /// Set the description. This function is only available if no description (hash) was set.
563- pub fn description ( mut self , description : String ) -> InvoiceBuilder < tb:: True , H , T , C > {
553+ pub fn description ( mut self , description : String ) -> InvoiceBuilder < tb:: True , H , T , C , S > {
564554 match Description :: new ( description) {
565555 Ok ( d) => self . tagged_fields . push ( TaggedField :: Description ( d) ) ,
566556 Err ( e) => self . error = Some ( e) ,
@@ -569,23 +559,23 @@ impl<H: tb::Bool, T: tb::Bool, C: tb::Bool> InvoiceBuilder<tb::False, H, T, C> {
569559 }
570560
571561 /// Set the description hash. This function is only available if no description (hash) was set.
572- pub fn description_hash ( mut self , description_hash : sha256:: Hash ) -> InvoiceBuilder < tb:: True , H , T , C > {
562+ pub fn description_hash ( mut self , description_hash : sha256:: Hash ) -> InvoiceBuilder < tb:: True , H , T , C , S > {
573563 self . tagged_fields . push ( TaggedField :: DescriptionHash ( Sha256 ( description_hash) ) ) ;
574564 self . set_flags ( )
575565 }
576566}
577567
578- impl < D : tb:: Bool , T : tb:: Bool , C : tb:: Bool > InvoiceBuilder < D , tb:: False , T , C > {
568+ impl < D : tb:: Bool , T : tb:: Bool , C : tb:: Bool , S : tb :: Bool > InvoiceBuilder < D , tb:: False , T , C , S > {
579569 /// Set the payment hash. This function is only available if no payment hash was set.
580- pub fn payment_hash ( mut self , hash : sha256:: Hash ) -> InvoiceBuilder < D , tb:: True , T , C > {
570+ pub fn payment_hash ( mut self , hash : sha256:: Hash ) -> InvoiceBuilder < D , tb:: True , T , C , S > {
581571 self . tagged_fields . push ( TaggedField :: PaymentHash ( Sha256 ( hash) ) ) ;
582572 self . set_flags ( )
583573 }
584574}
585575
586- impl < D : tb:: Bool , H : tb:: Bool , C : tb:: Bool > InvoiceBuilder < D , H , tb:: False , C > {
576+ impl < D : tb:: Bool , H : tb:: Bool , C : tb:: Bool , S : tb :: Bool > InvoiceBuilder < D , H , tb:: False , C , S > {
587577 /// Sets the timestamp.
588- pub fn timestamp ( mut self , time : SystemTime ) -> InvoiceBuilder < D , H , tb:: True , C > {
578+ pub fn timestamp ( mut self , time : SystemTime ) -> InvoiceBuilder < D , H , tb:: True , C , S > {
589579 match PositiveTimestamp :: from_system_time ( time) {
590580 Ok ( t) => self . timestamp = Some ( t) ,
591581 Err ( e) => self . error = Some ( e) ,
@@ -595,22 +585,48 @@ impl<D: tb::Bool, H: tb::Bool, C: tb::Bool> InvoiceBuilder<D, H, tb::False, C> {
595585 }
596586
597587 /// Sets the timestamp to the current UNIX timestamp.
598- pub fn current_timestamp ( mut self ) -> InvoiceBuilder < D , H , tb:: True , C > {
588+ pub fn current_timestamp ( mut self ) -> InvoiceBuilder < D , H , tb:: True , C , S > {
599589 let now = PositiveTimestamp :: from_system_time ( SystemTime :: now ( ) ) ;
600590 self . timestamp = Some ( now. expect ( "for the foreseeable future this shouldn't happen" ) ) ;
601591 self . set_flags ( )
602592 }
603593}
604594
605- impl < D : tb:: Bool , H : tb:: Bool , T : tb:: Bool > InvoiceBuilder < D , H , T , tb:: False > {
595+ impl < D : tb:: Bool , H : tb:: Bool , T : tb:: Bool , S : tb :: Bool > InvoiceBuilder < D , H , T , tb:: False , S > {
606596 /// Sets `min_final_cltv_expiry`.
607- pub fn min_final_cltv_expiry ( mut self , min_final_cltv_expiry : u64 ) -> InvoiceBuilder < D , H , T , tb:: True > {
597+ pub fn min_final_cltv_expiry ( mut self , min_final_cltv_expiry : u64 ) -> InvoiceBuilder < D , H , T , tb:: True , S > {
608598 self . tagged_fields . push ( TaggedField :: MinFinalCltvExpiry ( MinFinalCltvExpiry ( min_final_cltv_expiry) ) ) ;
609599 self . set_flags ( )
610600 }
611601}
612602
613- impl InvoiceBuilder < tb:: True , tb:: True , tb:: True , tb:: True > {
603+ impl < D : tb:: Bool , H : tb:: Bool , T : tb:: Bool , C : tb:: Bool > InvoiceBuilder < D , H , T , C , tb:: False > {
604+ /// Sets the payment secret and relevant features.
605+ pub fn payment_secret ( mut self , payment_secret : PaymentSecret ) -> InvoiceBuilder < D , H , T , C , tb:: True > {
606+ let features = InvoiceFeatures :: empty ( )
607+ . set_variable_length_onion_required ( )
608+ . set_payment_secret_required ( ) ;
609+ self . tagged_fields . push ( TaggedField :: PaymentSecret ( payment_secret) ) ;
610+ self . tagged_fields . push ( TaggedField :: Features ( features) ) ;
611+ self . set_flags ( )
612+ }
613+ }
614+
615+ impl < D : tb:: Bool , H : tb:: Bool , T : tb:: Bool , C : tb:: Bool > InvoiceBuilder < D , H , T , C , tb:: True > {
616+ /// Sets the `basic_mpp` feature as optional.
617+ pub fn basic_mpp ( mut self ) -> Self {
618+ self . tagged_fields = self . tagged_fields
619+ . drain ( ..)
620+ . map ( |field| match field {
621+ TaggedField :: Features ( f) => TaggedField :: Features ( f. set_basic_mpp_optional ( ) ) ,
622+ _ => field,
623+ } )
624+ . collect ( ) ;
625+ self
626+ }
627+ }
628+
629+ impl < S : tb:: Bool > InvoiceBuilder < tb:: True , tb:: True , tb:: True , tb:: True , S > {
614630 /// Builds and signs an invoice using the supplied `sign_function`. This function MAY NOT fail
615631 /// and MUST produce a recoverable signature valid for the given hash and if applicable also for
616632 /// the included payee public key.
@@ -649,6 +665,7 @@ impl InvoiceBuilder<tb::True, tb::True, tb::True, tb::True> {
649665 } ;
650666
651667 invoice. check_field_counts ( ) . expect ( "should be ensured by type signature of builder" ) ;
668+ invoice. check_feature_bits ( ) . expect ( "should be ensured by type signature of builder" ) ;
652669
653670 Ok ( invoice)
654671 }
@@ -977,6 +994,41 @@ impl Invoice {
977994 Ok ( ( ) )
978995 }
979996
997+ /// Check that feature bits are set as required
998+ fn check_feature_bits ( & self ) -> Result < ( ) , SemanticError > {
999+ // "If the payment_secret feature is set, MUST include exactly one s field."
1000+ let payment_secret_count = self . tagged_fields ( ) . filter ( |& tf| match * tf {
1001+ TaggedField :: PaymentSecret ( _) => true ,
1002+ _ => false ,
1003+ } ) . count ( ) ;
1004+ if payment_secret_count > 1 {
1005+ return Err ( SemanticError :: MultiplePaymentSecrets ) ;
1006+ }
1007+
1008+ // "A writer MUST set an s field if and only if the payment_secret feature is set."
1009+ let has_payment_secret = payment_secret_count == 1 ;
1010+ let features = self . tagged_fields ( ) . find ( |& tf| match * tf {
1011+ TaggedField :: Features ( _) => true ,
1012+ _ => false ,
1013+ } ) ;
1014+ match features {
1015+ None if has_payment_secret => Err ( SemanticError :: InvalidFeatures ) ,
1016+ None => Ok ( ( ) ) ,
1017+ Some ( TaggedField :: Features ( features) ) => {
1018+ if features. supports_payment_secret ( ) && has_payment_secret {
1019+ Ok ( ( ) )
1020+ } else if has_payment_secret {
1021+ Err ( SemanticError :: InvalidFeatures )
1022+ } else if features. supports_payment_secret ( ) {
1023+ Err ( SemanticError :: InvalidFeatures )
1024+ } else {
1025+ Ok ( ( ) )
1026+ }
1027+ } ,
1028+ Some ( _) => unreachable ! ( ) ,
1029+ }
1030+ }
1031+
9801032 /// Check that the invoice is signed correctly and that key recovery works
9811033 pub fn check_signature ( & self ) -> Result < ( ) , SemanticError > {
9821034 match self . signed_invoice . recover_payee_pub_key ( ) {
@@ -1011,6 +1063,7 @@ impl Invoice {
10111063 signed_invoice : signed_invoice,
10121064 } ;
10131065 invoice. check_field_counts ( ) ?;
1066+ invoice. check_feature_bits ( ) ?;
10141067 invoice. check_signature ( ) ?;
10151068
10161069 Ok ( invoice)
@@ -1303,6 +1356,12 @@ pub enum SemanticError {
13031356 /// The invoice contains multiple descriptions and/or description hashes which isn't allowed
13041357 MultipleDescriptions ,
13051358
1359+ /// The invoice contains multiple payment secrets
1360+ MultiplePaymentSecrets ,
1361+
1362+ /// The invoice's features are invalid
1363+ InvalidFeatures ,
1364+
13061365 /// The recovery id doesn't fit the signature/pub key
13071366 InvalidRecoveryId ,
13081367
@@ -1317,6 +1376,8 @@ impl Display for SemanticError {
13171376 SemanticError :: MultiplePaymentHashes => f. write_str ( "The invoice has multiple payment hashes which isn't allowed" ) ,
13181377 SemanticError :: NoDescription => f. write_str ( "No description or description hash are part of the invoice" ) ,
13191378 SemanticError :: MultipleDescriptions => f. write_str ( "The invoice contains multiple descriptions and/or description hashes which isn't allowed" ) ,
1379+ SemanticError :: MultiplePaymentSecrets => f. write_str ( "The invoice contains multiple payment secrets" ) ,
1380+ SemanticError :: InvalidFeatures => f. write_str ( "The invoice's features are invalid" ) ,
13201381 SemanticError :: InvalidRecoveryId => f. write_str ( "The recovery id doesn't fit the signature/pub key" ) ,
13211382 SemanticError :: InvalidSignature => f. write_str ( "The invoice's signature is invalid" ) ,
13221383 }
@@ -1467,6 +1528,97 @@ mod test {
14671528 assert ! ( new_signed. check_signature( ) ) ;
14681529 }
14691530
1531+ #[ test]
1532+ fn test_check_feature_bits ( ) {
1533+ use TaggedField :: * ;
1534+ use lightning:: ln:: features:: InvoiceFeatures ;
1535+ use secp256k1:: Secp256k1 ;
1536+ use secp256k1:: key:: SecretKey ;
1537+ use { RawInvoice , RawHrp , RawDataPart , Currency , Sha256 , PositiveTimestamp , Invoice ,
1538+ SemanticError } ;
1539+
1540+ let private_key = SecretKey :: from_slice ( & [ 42 ; 32 ] ) . unwrap ( ) ;
1541+ let payment_secret = lightning:: ln:: PaymentSecret ( [ 21 ; 32 ] ) ;
1542+ let invoice_template = RawInvoice {
1543+ hrp : RawHrp {
1544+ currency : Currency :: Bitcoin ,
1545+ raw_amount : None ,
1546+ si_prefix : None ,
1547+ } ,
1548+ data : RawDataPart {
1549+ timestamp : PositiveTimestamp :: from_unix_timestamp ( 1496314658 ) . unwrap ( ) ,
1550+ tagged_fields : vec ! [
1551+ PaymentHash ( Sha256 ( sha256:: Hash :: from_hex(
1552+ "0001020304050607080900010203040506070809000102030405060708090102"
1553+ ) . unwrap( ) ) ) . into( ) ,
1554+ Description (
1555+ :: Description :: new(
1556+ "Please consider supporting this project" . to_owned( )
1557+ ) . unwrap( )
1558+ ) . into( ) ,
1559+ ] ,
1560+ } ,
1561+ } ;
1562+
1563+ // Missing features
1564+ let invoice = {
1565+ let mut invoice = invoice_template. clone ( ) ;
1566+ invoice. data . tagged_fields . push ( PaymentSecret ( payment_secret) . into ( ) ) ;
1567+ invoice. sign :: < _ , ( ) > ( |hash| Ok ( Secp256k1 :: new ( ) . sign_recoverable ( hash, & private_key) ) )
1568+ } . unwrap ( ) ;
1569+ assert_eq ! ( Invoice :: from_signed( invoice) , Err ( SemanticError :: InvalidFeatures ) ) ;
1570+
1571+ // Missing feature bits
1572+ let invoice = {
1573+ let mut invoice = invoice_template. clone ( ) ;
1574+ invoice. data . tagged_fields . push ( PaymentSecret ( payment_secret) . into ( ) ) ;
1575+ invoice. data . tagged_fields . push ( Features ( InvoiceFeatures :: empty ( ) ) . into ( ) ) ;
1576+ invoice. sign :: < _ , ( ) > ( |hash| Ok ( Secp256k1 :: new ( ) . sign_recoverable ( hash, & private_key) ) )
1577+ } . unwrap ( ) ;
1578+ assert_eq ! ( Invoice :: from_signed( invoice) , Err ( SemanticError :: InvalidFeatures ) ) ;
1579+
1580+ // Including payment secret and feature bits
1581+ let invoice = {
1582+ let mut invoice = invoice_template. clone ( ) ;
1583+ invoice. data . tagged_fields . push ( PaymentSecret ( payment_secret) . into ( ) ) ;
1584+ invoice. data . tagged_fields . push ( Features ( InvoiceFeatures :: known ( ) ) . into ( ) ) ;
1585+ invoice. sign :: < _ , ( ) > ( |hash| Ok ( Secp256k1 :: new ( ) . sign_recoverable ( hash, & private_key) ) )
1586+ } . unwrap ( ) ;
1587+ assert ! ( Invoice :: from_signed( invoice) . is_ok( ) ) ;
1588+
1589+ // No payment secret or features
1590+ let invoice = {
1591+ let invoice = invoice_template. clone ( ) ;
1592+ invoice. sign :: < _ , ( ) > ( |hash| Ok ( Secp256k1 :: new ( ) . sign_recoverable ( hash, & private_key) ) )
1593+ } . unwrap ( ) ;
1594+ assert ! ( Invoice :: from_signed( invoice) . is_ok( ) ) ;
1595+
1596+ // No payment secret or feature bits
1597+ let invoice = {
1598+ let mut invoice = invoice_template. clone ( ) ;
1599+ invoice. data . tagged_fields . push ( Features ( InvoiceFeatures :: empty ( ) ) . into ( ) ) ;
1600+ invoice. sign :: < _ , ( ) > ( |hash| Ok ( Secp256k1 :: new ( ) . sign_recoverable ( hash, & private_key) ) )
1601+ } . unwrap ( ) ;
1602+ assert ! ( Invoice :: from_signed( invoice) . is_ok( ) ) ;
1603+
1604+ // Missing payment secret
1605+ let invoice = {
1606+ let mut invoice = invoice_template. clone ( ) ;
1607+ invoice. data . tagged_fields . push ( Features ( InvoiceFeatures :: known ( ) ) . into ( ) ) ;
1608+ invoice. sign :: < _ , ( ) > ( |hash| Ok ( Secp256k1 :: new ( ) . sign_recoverable ( hash, & private_key) ) )
1609+ } . unwrap ( ) ;
1610+ assert_eq ! ( Invoice :: from_signed( invoice) , Err ( SemanticError :: InvalidFeatures ) ) ;
1611+
1612+ // Multiple payment secrets
1613+ let invoice = {
1614+ let mut invoice = invoice_template. clone ( ) ;
1615+ invoice. data . tagged_fields . push ( PaymentSecret ( payment_secret) . into ( ) ) ;
1616+ invoice. data . tagged_fields . push ( PaymentSecret ( payment_secret) . into ( ) ) ;
1617+ invoice. sign :: < _ , ( ) > ( |hash| Ok ( Secp256k1 :: new ( ) . sign_recoverable ( hash, & private_key) ) )
1618+ } . unwrap ( ) ;
1619+ assert_eq ! ( Invoice :: from_signed( invoice) , Err ( SemanticError :: MultiplePaymentSecrets ) ) ;
1620+ }
1621+
14701622 #[ test]
14711623 fn test_builder_amount ( ) {
14721624 use :: * ;
@@ -1624,14 +1776,16 @@ mod test {
16241776 . route ( route_1. clone ( ) )
16251777 . route ( route_2. clone ( ) )
16261778 . description_hash ( sha256:: Hash :: from_slice ( & [ 3 ; 32 ] [ ..] ) . unwrap ( ) )
1627- . payment_hash ( sha256:: Hash :: from_slice ( & [ 21 ; 32 ] [ ..] ) . unwrap ( ) ) ;
1779+ . payment_hash ( sha256:: Hash :: from_slice ( & [ 21 ; 32 ] [ ..] ) . unwrap ( ) )
1780+ . payment_secret ( PaymentSecret ( [ 42 ; 32 ] ) )
1781+ . basic_mpp ( ) ;
16281782
16291783 let invoice = builder. clone ( ) . build_signed ( |hash| {
16301784 secp_ctx. sign_recoverable ( hash, & private_key)
16311785 } ) . unwrap ( ) ;
16321786
16331787 assert ! ( invoice. check_signature( ) . is_ok( ) ) ;
1634- assert_eq ! ( invoice. tagged_fields( ) . count( ) , 8 ) ;
1788+ assert_eq ! ( invoice. tagged_fields( ) . count( ) , 10 ) ;
16351789
16361790 assert_eq ! ( invoice. amount_pico_btc( ) , Some ( 123 ) ) ;
16371791 assert_eq ! ( invoice. currency( ) , Currency :: BitcoinTestnet ) ;
@@ -1649,6 +1803,8 @@ mod test {
16491803 InvoiceDescription :: Hash ( & Sha256 ( sha256:: Hash :: from_slice( & [ 3 ; 32 ] [ ..] ) . unwrap( ) ) )
16501804 ) ;
16511805 assert_eq ! ( invoice. payment_hash( ) , & sha256:: Hash :: from_slice( & [ 21 ; 32 ] [ ..] ) . unwrap( ) ) ;
1806+ assert_eq ! ( invoice. payment_secret( ) , Some ( & PaymentSecret ( [ 42 ; 32 ] ) ) ) ;
1807+ assert_eq ! ( invoice. features( ) , Some ( & InvoiceFeatures :: known( ) ) ) ;
16521808
16531809 let raw_invoice = builder. build_raw ( ) . unwrap ( ) ;
16541810 assert_eq ! ( raw_invoice, * invoice. into_signed_raw( ) . raw_invoice( ) )
0 commit comments