@@ -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,61 @@ impl RawInvoice {
717
757
}
718
758
}
719
759
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
+
720
815
impl Invoice {
721
816
fn into_signed_raw ( self ) -> SignedRawInvoice {
722
817
self . signed_invoice
@@ -788,8 +883,8 @@ impl Invoice {
788
883
Ok ( invoice)
789
884
}
790
885
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 ( )
793
888
}
794
889
795
890
/// Returns an iterator over all tagged fields of this Invoice.
@@ -967,6 +1062,9 @@ pub enum CreationError {
967
1062
968
1063
/// The specified route has too many hops and can't be encoded
969
1064
RouteTooLong ,
1065
+
1066
+ /// The unix timestamp of the supplied date is <0 or can't be represented as `SystemTime`
1067
+ TimestampOutOfBounds ,
970
1068
}
971
1069
972
1070
/// Errors that may occur when converting a `RawInvoice` to an `Invoice`. They relate to the
@@ -997,9 +1095,23 @@ mod test {
997
1095
use bitcoin_hashes:: hex:: FromHex ;
998
1096
use bitcoin_hashes:: sha256:: Sha256Hash ;
999
1097
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
+
1000
1112
#[ test]
1001
1113
fn test_calc_invoice_hash ( ) {
1002
- use :: { RawInvoice , RawHrp , RawDataPart , Currency } ;
1114
+ use :: { RawInvoice , RawHrp , RawDataPart , Currency , PositiveTimestamp } ;
1003
1115
use secp256k1:: * ;
1004
1116
use :: TaggedField :: * ;
1005
1117
@@ -1010,7 +1122,7 @@ mod test {
1010
1122
si_prefix : None ,
1011
1123
} ,
1012
1124
data : RawDataPart {
1013
- timestamp : 1496314658 ,
1125
+ timestamp : PositiveTimestamp :: from_unix_timestamp ( 1496314658 ) . unwrap ( ) ,
1014
1126
tagged_fields : vec ! [
1015
1127
PaymentHash ( :: Sha256 ( Sha256Hash :: from_hex(
1016
1128
"0001020304050607080900010203040506070809000102030405060708090102"
@@ -1036,7 +1148,8 @@ mod test {
1036
1148
use TaggedField :: * ;
1037
1149
use secp256k1:: { RecoveryId , RecoverableSignature , Secp256k1 } ;
1038
1150
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 } ;
1040
1153
1041
1154
let mut invoice = SignedRawInvoice {
1042
1155
raw_invoice : RawInvoice {
@@ -1046,7 +1159,7 @@ mod test {
1046
1159
si_prefix : None ,
1047
1160
} ,
1048
1161
data : RawDataPart {
1049
- timestamp : 1496314658 ,
1162
+ timestamp : PositiveTimestamp :: from_unix_timestamp ( 1496314658 ) . unwrap ( ) ,
1050
1163
tagged_fields : vec ! [
1051
1164
PaymentHash ( Sha256 ( Sha256Hash :: from_hex(
1052
1165
"0001020304050607080900010203040506070809000102030405060708090102"
@@ -1181,6 +1294,7 @@ mod test {
1181
1294
use :: * ;
1182
1295
use secp256k1:: Secp256k1 ;
1183
1296
use secp256k1:: key:: { SecretKey , PublicKey } ;
1297
+ use std:: time:: UNIX_EPOCH ;
1184
1298
1185
1299
let secp_ctx = Secp256k1 :: new ( ) ;
1186
1300
@@ -1230,7 +1344,7 @@ mod test {
1230
1344
1231
1345
let builder = InvoiceBuilder :: new ( Currency :: BitcoinTestnet )
1232
1346
. amount_pico_btc ( 123 )
1233
- . timestamp ( 1234567 )
1347
+ . timestamp_raw ( 1234567 )
1234
1348
. payee_pub_key ( public_key. clone ( ) )
1235
1349
. expiry_time_seconds ( 54321 )
1236
1350
. min_final_cltv_expiry ( 144 )
@@ -1250,7 +1364,10 @@ mod test {
1250
1364
1251
1365
assert_eq ! ( invoice. amount_pico_btc( ) , Some ( 123 ) ) ;
1252
1366
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
+ ) ;
1254
1371
assert_eq ! ( invoice. payee_pub_key( ) , Some ( & PayeePubKey ( public_key) ) ) ;
1255
1372
assert_eq ! ( invoice. expiry_time( ) , Some ( & ExpiryTime { seconds: 54321 } ) ) ;
1256
1373
assert_eq ! ( invoice. min_final_cltv_expiry( ) , Some ( & MinFinalCltvExpiry ( 144 ) ) ) ;
0 commit comments