@@ -13,13 +13,32 @@ use std::ops::Deref;
1313
1414use std:: iter:: FilterMap ;
1515use std:: slice:: Iter ;
16+ use std:: time:: { SystemTime , Duration , UNIX_EPOCH } ;
1617
1718mod de;
1819mod ser;
1920mod tb;
2021
2122pub use de:: { ParseError , ParseOrSemanticError } ;
2223
24+
25+ // TODO: fix before 2038 (see rust PR #55527)
26+ /// Defines the maximum UNIX timestamp that can be represented as `SystemTime`. This is checked by
27+ /// one of the unit tests, please run them.
28+ const SYSTEM_TIME_MAX_UNIX_TIMESTAMP : u64 = std:: i32:: MAX as u64 ;
29+
30+ /// This function is used as a static assert for the size of `SystemTime`. If the crate fails to
31+ /// compile due to it this indicates that your system uses unexpected bounds for `SystemTime`. You
32+ /// can remove this functions and run the test `test_system_time_bounds_assumptions`. In any case,
33+ /// please open an issue. If all tests pass you should be able to use this library safely by just
34+ /// removing this function till we patch it accordingly.
35+ fn __system_time_size_check ( ) {
36+ /// Use 2 * sizeof(u64) as expected size since the expected underlying implementation is storing
37+ /// a `Duration` since `SystemTime::UNIX_EPOCH`.
38+ unsafe { std:: mem:: transmute :: < SystemTime , [ u8 ; 16 ] > ( UNIX_EPOCH ) ; }
39+ }
40+
41+
2342/// Builder for `Invoice`s. It's the most convenient and advised way to use this library. It ensures
2443/// that only a semantically and syntactically correct Invoice can be built using it.
2544///
@@ -72,7 +91,7 @@ pub struct InvoiceBuilder<D: tb::Bool, H: tb::Bool, T: tb::Bool> {
7291 currency : Currency ,
7392 amount : Option < u64 > ,
7493 si_prefix : Option < SiPrefix > ,
75- timestamp : Option < u64 > ,
94+ timestamp : Option < PositiveTimestamp > ,
7695 tagged_fields : Vec < TaggedField > ,
7796 error : Option < CreationError > ,
7897
@@ -149,14 +168,22 @@ pub struct RawHrp {
149168/// Data of the `RawInvoice` that is encoded in the data part
150169#[ derive( Eq , PartialEq , Debug , Clone ) ]
151170pub struct RawDataPart {
152- // TODO: find better fitting type that only allows positive timestamps to avoid checks for negative timestamps when encoding
153- /// generation time of the invoice as UNIX timestamp
154- pub timestamp : u64 ,
171+ /// generation time of the invoice
172+ pub timestamp : PositiveTimestamp ,
155173
156174 /// tagged fields of the payment request
157175 pub tagged_fields : Vec < RawTaggedField > ,
158176}
159177
178+ /// A timestamp that refers to a date after 1 January 1970 which means its representation as UNIX
179+ /// timestamp is positive.
180+ ///
181+ /// # Invariants
182+ /// The UNIX timestamp representing the stored time has to be positive and small enough so that
183+ /// a `EpiryTime` can be added to it without an overflow.
184+ #[ derive( Eq , PartialEq , Debug , Clone ) ]
185+ pub struct PositiveTimestamp ( SystemTime ) ;
186+
160187/// SI prefixes for the human readable part
161188#[ derive( Eq , PartialEq , Debug , Clone , Copy ) ]
162189pub enum SiPrefix {
@@ -442,17 +469,30 @@ impl<D: tb::Bool, T: tb::Bool> InvoiceBuilder<D, tb::False, T> {
442469
443470impl < D : tb:: Bool , H : tb:: Bool > InvoiceBuilder < D , H , tb:: False > {
444471 /// Sets the timestamp. `time` is a UNIX timestamp.
445- pub fn timestamp ( mut self , time : u64 ) -> InvoiceBuilder < D , H , tb:: True > {
446- self . timestamp = Some ( time) ;
472+ pub fn timestamp_raw ( mut self , time : u64 ) -> InvoiceBuilder < D , H , tb:: True > {
473+ match PositiveTimestamp :: from_unix_timestamp ( time) {
474+ Ok ( t) => self . timestamp = Some ( t) ,
475+ Err ( e) => self . error = Some ( e) ,
476+ }
477+
478+ self . set_flags ( )
479+ }
480+
481+ /// Sets the timestamp.
482+ pub fn timestamp ( mut self , time : SystemTime ) -> InvoiceBuilder < D , H , tb:: True > {
483+ match PositiveTimestamp :: from_system_time ( time) {
484+ Ok ( t) => self . timestamp = Some ( t) ,
485+ Err ( e) => self . error = Some ( e) ,
486+ }
487+
447488 self . set_flags ( )
448489 }
449490
450491 /// Sets the timestamp to the current UNIX timestamp.
451492 pub fn current_timestamp ( mut self ) -> InvoiceBuilder < D , H , tb:: True > {
452493 use std:: time:: { SystemTime , UNIX_EPOCH } ;
453- let now = SystemTime :: now ( ) ;
454- let since_unix_epoch = now. duration_since ( UNIX_EPOCH ) . expect ( "it won't be 1970 ever again" ) ;
455- self . timestamp = Some ( since_unix_epoch. as_secs ( ) as u64 ) ;
494+ let now = PositiveTimestamp :: from_system_time ( SystemTime :: now ( ) ) ;
495+ self . timestamp = Some ( now. expect ( "for the foreseeable future this shouldn't happen" ) ) ;
456496 self . set_flags ( )
457497 }
458498}
@@ -717,6 +757,61 @@ impl RawInvoice {
717757 }
718758}
719759
760+ impl PositiveTimestamp {
761+
762+ /// Create a new `PositiveTimestamp` from a unix timestamp in the Range
763+ /// `0...SYSTEM_TIME_MAX_UNIX_TIMESTAMP / 2`, otherwise return a
764+ /// `CreationError::TimestampOutOfBounds`.
765+ pub fn from_unix_timestamp ( unix_seconds : u64 ) -> Result < Self , CreationError > {
766+ if unix_seconds > SYSTEM_TIME_MAX_UNIX_TIMESTAMP / 2 {
767+ Err ( CreationError :: TimestampOutOfBounds )
768+ } else {
769+ Ok ( PositiveTimestamp ( UNIX_EPOCH + Duration :: from_secs ( unix_seconds) ) )
770+ }
771+ }
772+
773+ /// Create a new `PositiveTimestamp` from a `SystemTime` with a corresponding unix timestamp in
774+ /// the Range `0...SYSTEM_TIME_MAX_UNIX_TIMESTAMP / 2`, otherwise return a
775+ /// `CreationError::TimestampOutOfBounds`.
776+ pub fn from_system_time ( time : SystemTime ) -> Result < Self , CreationError > {
777+ if time
778+ . duration_since ( UNIX_EPOCH )
779+ . map ( |t| t. as_secs ( ) <= SYSTEM_TIME_MAX_UNIX_TIMESTAMP / 2 ) // check for consistency reasons
780+ . unwrap_or ( true )
781+ {
782+ Ok ( PositiveTimestamp ( time) )
783+ } else {
784+ Err ( CreationError :: TimestampOutOfBounds )
785+ }
786+ }
787+
788+ /// Returns the UNIX timestamp representing the stored time
789+ pub fn as_unix_timestamp ( & self ) -> u64 {
790+ self . 0 . duration_since ( UNIX_EPOCH )
791+ . expect ( "ensured by type contract/constructors" )
792+ . as_secs ( )
793+ }
794+
795+ /// Returns a reference to the internal `SystemTime` time representation
796+ pub fn as_time ( & self ) -> & SystemTime {
797+ & self . 0
798+ }
799+ }
800+
801+ impl Into < SystemTime > for PositiveTimestamp {
802+ fn into ( self ) -> SystemTime {
803+ self . 0
804+ }
805+ }
806+
807+ impl Deref for PositiveTimestamp {
808+ type Target = SystemTime ;
809+
810+ fn deref ( & self ) -> & Self :: Target {
811+ & self . 0
812+ }
813+ }
814+
720815impl Invoice {
721816 fn into_signed_raw ( self ) -> SignedRawInvoice {
722817 self . signed_invoice
@@ -788,8 +883,8 @@ impl Invoice {
788883 Ok ( invoice)
789884 }
790885
791- pub fn timestamp ( & self ) -> u64 {
792- self . signed_invoice . raw_invoice . data . timestamp
886+ pub fn timestamp ( & self ) -> & SystemTime {
887+ self . signed_invoice . raw_invoice ( ) . data . timestamp . as_time ( )
793888 }
794889
795890 /// Returns an iterator over all tagged fields of this Invoice.
@@ -967,6 +1062,9 @@ pub enum CreationError {
9671062
9681063 /// The specified route has too many hops and can't be encoded
9691064 RouteTooLong ,
1065+
1066+ /// The unix timestamp of the supplied date is <0 or can't be represented as `SystemTime`
1067+ TimestampOutOfBounds ,
9701068}
9711069
9721070/// Errors that may occur when converting a `RawInvoice` to an `Invoice`. They relate to the
@@ -997,9 +1095,23 @@ mod test {
9971095 use bitcoin_hashes:: hex:: FromHex ;
9981096 use bitcoin_hashes:: sha256:: Sha256Hash ;
9991097
1098+ #[ test]
1099+ fn test_system_time_bounds_assumptions ( ) {
1100+ use std:: time:: { Duration , SystemTime , UNIX_EPOCH } ;
1101+
1102+ /// The upper and lower bounds of `SystemTime` are not part of its public contract and are
1103+ /// platform specific. That's why we have to test if our assumptions regarding these bounds
1104+ /// hold on the target platform.
1105+ ///
1106+ /// If this test fails on your platform, please don't use the library and open an issue
1107+ /// instead so we can resolve the situation. Currently this library is tested on:
1108+ /// * Linux (64bit)
1109+ let _ = UNIX_EPOCH + Duration :: from_secs ( :: std:: i64:: MAX as u64 ) ;
1110+ }
1111+
10001112 #[ test]
10011113 fn test_calc_invoice_hash ( ) {
1002- use :: { RawInvoice , RawHrp , RawDataPart , Currency } ;
1114+ use :: { RawInvoice , RawHrp , RawDataPart , Currency , PositiveTimestamp } ;
10031115 use secp256k1:: * ;
10041116 use :: TaggedField :: * ;
10051117
@@ -1010,7 +1122,7 @@ mod test {
10101122 si_prefix : None ,
10111123 } ,
10121124 data : RawDataPart {
1013- timestamp : 1496314658 ,
1125+ timestamp : PositiveTimestamp :: from_unix_timestamp ( 1496314658 ) . unwrap ( ) ,
10141126 tagged_fields : vec ! [
10151127 PaymentHash ( :: Sha256 ( Sha256Hash :: from_hex(
10161128 "0001020304050607080900010203040506070809000102030405060708090102"
@@ -1036,7 +1148,8 @@ mod test {
10361148 use TaggedField :: * ;
10371149 use secp256k1:: { RecoveryId , RecoverableSignature , Secp256k1 } ;
10381150 use secp256k1:: key:: { SecretKey , PublicKey } ;
1039- use { SignedRawInvoice , Signature , RawInvoice , RawHrp , RawDataPart , Currency , Sha256 } ;
1151+ use { SignedRawInvoice , Signature , RawInvoice , RawHrp , RawDataPart , Currency , Sha256 ,
1152+ PositiveTimestamp } ;
10401153
10411154 let mut invoice = SignedRawInvoice {
10421155 raw_invoice : RawInvoice {
@@ -1046,7 +1159,7 @@ mod test {
10461159 si_prefix : None ,
10471160 } ,
10481161 data : RawDataPart {
1049- timestamp : 1496314658 ,
1162+ timestamp : PositiveTimestamp :: from_unix_timestamp ( 1496314658 ) . unwrap ( ) ,
10501163 tagged_fields : vec ! [
10511164 PaymentHash ( Sha256 ( Sha256Hash :: from_hex(
10521165 "0001020304050607080900010203040506070809000102030405060708090102"
@@ -1181,6 +1294,7 @@ mod test {
11811294 use :: * ;
11821295 use secp256k1:: Secp256k1 ;
11831296 use secp256k1:: key:: { SecretKey , PublicKey } ;
1297+ use std:: time:: UNIX_EPOCH ;
11841298
11851299 let secp_ctx = Secp256k1 :: new ( ) ;
11861300
@@ -1230,7 +1344,7 @@ mod test {
12301344
12311345 let builder = InvoiceBuilder :: new ( Currency :: BitcoinTestnet )
12321346 . amount_pico_btc ( 123 )
1233- . timestamp ( 1234567 )
1347+ . timestamp_raw ( 1234567 )
12341348 . payee_pub_key ( public_key. clone ( ) )
12351349 . expiry_time_seconds ( 54321 )
12361350 . min_final_cltv_expiry ( 144 )
@@ -1250,7 +1364,10 @@ mod test {
12501364
12511365 assert_eq ! ( invoice. amount_pico_btc( ) , Some ( 123 ) ) ;
12521366 assert_eq ! ( invoice. currency( ) , Currency :: BitcoinTestnet ) ;
1253- assert_eq ! ( invoice. timestamp( ) , 1234567 ) ;
1367+ assert_eq ! (
1368+ invoice. timestamp( ) . duration_since( UNIX_EPOCH ) . unwrap( ) . as_secs( ) ,
1369+ 1234567
1370+ ) ;
12541371 assert_eq ! ( invoice. payee_pub_key( ) , Some ( & PayeePubKey ( public_key) ) ) ;
12551372 assert_eq ! ( invoice. expiry_time( ) , Some ( & ExpiryTime { seconds: 54321 } ) ) ;
12561373 assert_eq ! ( invoice. min_final_cltv_expiry( ) , Some ( & MinFinalCltvExpiry ( 144 ) ) ) ;
0 commit comments