@@ -13,13 +13,32 @@ use std::ops::Deref;
13
13
14
14
use std:: iter:: FilterMap ;
15
15
use std:: slice:: Iter ;
16
+ use std:: time:: { SystemTime , Duration , UNIX_EPOCH } ;
16
17
17
18
mod de;
18
19
mod ser;
19
20
mod tb;
20
21
21
22
pub use de:: { ParseError , ParseOrSemanticError } ;
22
23
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
+
23
42
/// Builder for `Invoice`s. It's the most convenient and advised way to use this library. It ensures
24
43
/// that only a semantically and syntactically correct Invoice can be built using it.
25
44
///
@@ -72,7 +91,7 @@ pub struct InvoiceBuilder<D: tb::Bool, H: tb::Bool, T: tb::Bool> {
72
91
currency : Currency ,
73
92
amount : Option < u64 > ,
74
93
si_prefix : Option < SiPrefix > ,
75
- timestamp : Option < u64 > ,
94
+ timestamp : Option < PositiveTimestamp > ,
76
95
tagged_fields : Vec < TaggedField > ,
77
96
error : Option < CreationError > ,
78
97
@@ -149,14 +168,22 @@ pub struct RawHrp {
149
168
/// Data of the `RawInvoice` that is encoded in the data part
150
169
#[ derive( Eq , PartialEq , Debug , Clone ) ]
151
170
pub 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 ,
155
173
156
174
/// tagged fields of the payment request
157
175
pub tagged_fields : Vec < RawTaggedField > ,
158
176
}
159
177
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
+
160
187
/// SI prefixes for the human readable part
161
188
#[ derive( Eq , PartialEq , Debug , Clone , Copy ) ]
162
189
pub enum SiPrefix {
@@ -442,17 +469,30 @@ impl<D: tb::Bool, T: tb::Bool> InvoiceBuilder<D, tb::False, T> {
442
469
443
470
impl < D : tb:: Bool , H : tb:: Bool > InvoiceBuilder < D , H , tb:: False > {
444
471
/// 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
+
447
488
self . set_flags ( )
448
489
}
449
490
450
491
/// Sets the timestamp to the current UNIX timestamp.
451
492
pub fn current_timestamp ( mut self ) -> InvoiceBuilder < D , H , tb:: True > {
452
493
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" ) ) ;
456
496
self . set_flags ( )
457
497
}
458
498
}
@@ -717,6 +757,60 @@ impl RawInvoice {
717
757
}
718
758
}
719
759
760
+ impl PositiveTimestamp {
761
+ /// Create a new `PositiveTimestamp` from a unix timestamp in the Range
762
+ /// `0...SYSTEM_TIME_MAX_UNIX_TIMESTAMP`, otherwise return a
763
+ /// `CreationError::TimestampOutOfBounds`.
764
+ pub fn from_unix_timestamp ( unix_seconds : u64 ) -> Result < Self , CreationError > {
765
+ if unix_seconds > SYSTEM_TIME_MAX_UNIX_TIMESTAMP {
766
+ Err ( CreationError :: TimestampOutOfBounds )
767
+ } else {
768
+ Ok ( PositiveTimestamp ( UNIX_EPOCH + Duration :: from_secs ( unix_seconds) ) )
769
+ }
770
+ }
771
+
772
+ /// Create a new `PositiveTimestamp` from a `SystemTime` with a corresponding unix timestamp in
773
+ /// the Range `0...SYSTEM_TIME_MAX_UNIX_TIMESTAMP`, otherwise return a
774
+ /// `CreationError::TimestampOutOfBounds`.
775
+ pub fn from_system_time ( time : SystemTime ) -> Result < Self , CreationError > {
776
+ if time
777
+ . duration_since ( UNIX_EPOCH )
778
+ . map ( |t| t. as_secs ( ) <= SYSTEM_TIME_MAX_UNIX_TIMESTAMP ) // check for consistency reasons
779
+ . unwrap_or ( true )
780
+ {
781
+ Ok ( PositiveTimestamp ( time) )
782
+ } else {
783
+ Err ( CreationError :: TimestampOutOfBounds )
784
+ }
785
+ }
786
+
787
+ /// Returns the UNIX timestamp representing the stored time
788
+ pub fn as_unix_timestamp ( & self ) -> u64 {
789
+ self . 0 . duration_since ( UNIX_EPOCH )
790
+ . expect ( "ensured by type contract/constructors" )
791
+ . as_secs ( )
792
+ }
793
+
794
+ /// Returns a reference to the internal `SystemTime` time representation
795
+ pub fn as_time ( & self ) -> & SystemTime {
796
+ & self . 0
797
+ }
798
+ }
799
+
800
+ impl Into < SystemTime > for PositiveTimestamp {
801
+ fn into ( self ) -> SystemTime {
802
+ self . 0
803
+ }
804
+ }
805
+
806
+ impl Deref for PositiveTimestamp {
807
+ type Target = SystemTime ;
808
+
809
+ fn deref ( & self ) -> & Self :: Target {
810
+ & self . 0
811
+ }
812
+ }
813
+
720
814
impl Invoice {
721
815
fn into_signed_raw ( self ) -> SignedRawInvoice {
722
816
self . signed_invoice
@@ -788,8 +882,8 @@ impl Invoice {
788
882
Ok ( invoice)
789
883
}
790
884
791
- pub fn timestamp ( & self ) -> u64 {
792
- self . signed_invoice . raw_invoice . data . timestamp
885
+ pub fn timestamp ( & self ) -> & SystemTime {
886
+ self . signed_invoice . raw_invoice ( ) . data . timestamp . as_time ( )
793
887
}
794
888
795
889
/// Returns an iterator over all tagged fields of this Invoice.
@@ -967,6 +1061,9 @@ pub enum CreationError {
967
1061
968
1062
/// The specified route has too many hops and can't be encoded
969
1063
RouteTooLong ,
1064
+
1065
+ /// The unix timestamp of the supplied date is <0 or can't be represented as `SystemTime`
1066
+ TimestampOutOfBounds ,
970
1067
}
971
1068
972
1069
/// Errors that may occur when converting a `RawInvoice` to an `Invoice`. They relate to the
@@ -997,9 +1094,27 @@ mod test {
997
1094
use bitcoin_hashes:: hex:: FromHex ;
998
1095
use bitcoin_hashes:: sha256:: Sha256Hash ;
999
1096
1097
+ #[ test]
1098
+ fn test_system_time_bounds_assumptions ( ) {
1099
+ use std:: time:: { Duration , SystemTime , UNIX_EPOCH } ;
1100
+
1101
+ // The upper and lower bounds of `SystemTime` are not part of its public contract and are
1102
+ // platform specific. That's why we have to test if our assumptions regarding these bounds
1103
+ // hold on the target platform.
1104
+ //
1105
+ // If this test fails on your platform, please don't use the library and open an issue
1106
+ // instead so we can resolve the situation. Currently this library is tested on:
1107
+ // * Linux (64bit)
1108
+ let fail_date = UNIX_EPOCH + Duration :: from_secs ( :: SYSTEM_TIME_MAX_UNIX_TIMESTAMP ) ;
1109
+ let year = Duration :: from_secs ( 60 * 60 * 24 * 365 ) ;
1110
+
1111
+ // Make sure that the library will keep working for another year
1112
+ assert ! ( fail_date. duration_since( SystemTime :: now( ) ) . unwrap( ) > year)
1113
+ }
1114
+
1000
1115
#[ test]
1001
1116
fn test_calc_invoice_hash ( ) {
1002
- use :: { RawInvoice , RawHrp , RawDataPart , Currency } ;
1117
+ use :: { RawInvoice , RawHrp , RawDataPart , Currency , PositiveTimestamp } ;
1003
1118
use secp256k1:: * ;
1004
1119
use :: TaggedField :: * ;
1005
1120
@@ -1010,7 +1125,7 @@ mod test {
1010
1125
si_prefix : None ,
1011
1126
} ,
1012
1127
data : RawDataPart {
1013
- timestamp : 1496314658 ,
1128
+ timestamp : PositiveTimestamp :: from_unix_timestamp ( 1496314658 ) . unwrap ( ) ,
1014
1129
tagged_fields : vec ! [
1015
1130
PaymentHash ( :: Sha256 ( Sha256Hash :: from_hex(
1016
1131
"0001020304050607080900010203040506070809000102030405060708090102"
@@ -1036,7 +1151,8 @@ mod test {
1036
1151
use TaggedField :: * ;
1037
1152
use secp256k1:: { RecoveryId , RecoverableSignature , Secp256k1 } ;
1038
1153
use secp256k1:: key:: { SecretKey , PublicKey } ;
1039
- use { SignedRawInvoice , Signature , RawInvoice , RawHrp , RawDataPart , Currency , Sha256 } ;
1154
+ use { SignedRawInvoice , Signature , RawInvoice , RawHrp , RawDataPart , Currency , Sha256 ,
1155
+ PositiveTimestamp } ;
1040
1156
1041
1157
let mut invoice = SignedRawInvoice {
1042
1158
raw_invoice : RawInvoice {
@@ -1046,7 +1162,7 @@ mod test {
1046
1162
si_prefix : None ,
1047
1163
} ,
1048
1164
data : RawDataPart {
1049
- timestamp : 1496314658 ,
1165
+ timestamp : PositiveTimestamp :: from_unix_timestamp ( 1496314658 ) . unwrap ( ) ,
1050
1166
tagged_fields : vec ! [
1051
1167
PaymentHash ( Sha256 ( Sha256Hash :: from_hex(
1052
1168
"0001020304050607080900010203040506070809000102030405060708090102"
@@ -1181,6 +1297,7 @@ mod test {
1181
1297
use :: * ;
1182
1298
use secp256k1:: Secp256k1 ;
1183
1299
use secp256k1:: key:: { SecretKey , PublicKey } ;
1300
+ use std:: time:: UNIX_EPOCH ;
1184
1301
1185
1302
let secp_ctx = Secp256k1 :: new ( ) ;
1186
1303
@@ -1230,7 +1347,7 @@ mod test {
1230
1347
1231
1348
let builder = InvoiceBuilder :: new ( Currency :: BitcoinTestnet )
1232
1349
. amount_pico_btc ( 123 )
1233
- . timestamp ( 1234567 )
1350
+ . timestamp_raw ( 1234567 )
1234
1351
. payee_pub_key ( public_key. clone ( ) )
1235
1352
. expiry_time_seconds ( 54321 )
1236
1353
. min_final_cltv_expiry ( 144 )
@@ -1250,7 +1367,10 @@ mod test {
1250
1367
1251
1368
assert_eq ! ( invoice. amount_pico_btc( ) , Some ( 123 ) ) ;
1252
1369
assert_eq ! ( invoice. currency( ) , Currency :: BitcoinTestnet ) ;
1253
- assert_eq ! ( invoice. timestamp( ) , 1234567 ) ;
1370
+ assert_eq ! (
1371
+ invoice. timestamp( ) . duration_since( UNIX_EPOCH ) . unwrap( ) . as_secs( ) ,
1372
+ 1234567
1373
+ ) ;
1254
1374
assert_eq ! ( invoice. payee_pub_key( ) , Some ( & PayeePubKey ( public_key) ) ) ;
1255
1375
assert_eq ! ( invoice. expiry_time( ) , Some ( & ExpiryTime { seconds: 54321 } ) ) ;
1256
1376
assert_eq ! ( invoice. min_final_cltv_expiry( ) , Some ( & MinFinalCltvExpiry ( 144 ) ) ) ;
0 commit comments