@@ -22,11 +22,15 @@ mod tb;
22
22
pub use de:: { ParseError , ParseOrSemanticError } ;
23
23
24
24
25
- // TODO: fix before 2038 (see rust PR #55527)
25
+ // TODO: fix before 2037 (see rust PR #55527)
26
26
/// Defines the maximum UNIX timestamp that can be represented as `SystemTime`. This is checked by
27
27
/// one of the unit tests, please run them.
28
28
const SYSTEM_TIME_MAX_UNIX_TIMESTAMP : u64 = std:: i32:: MAX as u64 ;
29
29
30
+ /// Allow the expiry time to be up to one year. Since this reduces the range of possible timestamps
31
+ /// it should be rather low as long as we still have to support 32bit time representations
32
+ const MAX_EXPIRY_TIME : u64 = 60 * 60 * 24 * 356 ;
33
+
30
34
/// This function is used as a static assert for the size of `SystemTime`. If the crate fails to
31
35
/// compile due to it this indicates that your system uses unexpected bounds for `SystemTime`. You
32
36
/// can remove this functions and run the test `test_system_time_bounds_assumptions`. In any case,
@@ -261,11 +265,15 @@ pub struct Description(String);
261
265
#[ derive( Eq , PartialEq , Debug , Clone ) ]
262
266
pub struct PayeePubKey ( pub PublicKey ) ;
263
267
264
- /// Positive duration that defines when (relatively to the timestamp) in the future the invoice expires
268
+ /// Positive duration that defines when (relatively to the timestamp) in the future the invoice
269
+ /// expires
270
+ ///
271
+ /// # Invariants
272
+ /// The number of seconds this expiry time represents has to be in the range
273
+ /// `0...(SYSTEM_TIME_MAX_UNIX_TIMESTAMP - MAX_EXPIRY_TIME)` to avoid overflows when adding it to a
274
+ /// timestamp
265
275
#[ derive( Eq , PartialEq , Debug , Clone ) ]
266
- pub struct ExpiryTime {
267
- pub seconds : u64
268
- }
276
+ pub struct ExpiryTime ( Duration ) ;
269
277
270
278
/// `min_final_cltv_expiry` to use for the last HTLC in the route
271
279
#[ derive( Eq , PartialEq , Debug , Clone ) ]
@@ -381,9 +389,12 @@ impl<D: tb::Bool, H: tb::Bool, T: tb::Bool> InvoiceBuilder<D, H, T> {
381
389
self
382
390
}
383
391
384
- /// Sets the expiry time in seconds.
385
- pub fn expiry_time_seconds ( mut self , expiry_seconds : u64 ) -> Self {
386
- self . tagged_fields . push ( TaggedField :: ExpiryTime ( ExpiryTime { seconds : expiry_seconds} ) ) ;
392
+ /// Sets the expiry time
393
+ pub fn expiry_time ( mut self , expiry_time : Duration ) -> Self {
394
+ match ExpiryTime :: from_duration ( expiry_time) {
395
+ Ok ( t) => self . tagged_fields . push ( TaggedField :: ExpiryTime ( t) ) ,
396
+ Err ( e) => self . error = Some ( e) ,
397
+ } ;
387
398
self
388
399
}
389
400
@@ -490,7 +501,7 @@ impl<D: tb::Bool, H: tb::Bool> InvoiceBuilder<D, H, tb::False> {
490
501
491
502
/// Sets the timestamp to the current UNIX timestamp.
492
503
pub fn current_timestamp ( mut self ) -> InvoiceBuilder < D , H , tb:: True > {
493
- use std:: time:: { SystemTime , UNIX_EPOCH } ;
504
+ use std:: time:: SystemTime ;
494
505
let now = PositiveTimestamp :: from_system_time ( SystemTime :: now ( ) ) ;
495
506
self . timestamp = Some ( now. expect ( "for the foreseeable future this shouldn't happen" ) ) ;
496
507
self . set_flags ( )
@@ -759,23 +770,23 @@ impl RawInvoice {
759
770
760
771
impl PositiveTimestamp {
761
772
/// Create a new `PositiveTimestamp` from a unix timestamp in the Range
762
- /// `0...SYSTEM_TIME_MAX_UNIX_TIMESTAMP`, otherwise return a
773
+ /// `0...SYSTEM_TIME_MAX_UNIX_TIMESTAMP - MAX_EXPIRY_TIME `, otherwise return a
763
774
/// `CreationError::TimestampOutOfBounds`.
764
775
pub fn from_unix_timestamp ( unix_seconds : u64 ) -> Result < Self , CreationError > {
765
- if unix_seconds > SYSTEM_TIME_MAX_UNIX_TIMESTAMP {
776
+ if unix_seconds > SYSTEM_TIME_MAX_UNIX_TIMESTAMP - MAX_EXPIRY_TIME {
766
777
Err ( CreationError :: TimestampOutOfBounds )
767
778
} else {
768
779
Ok ( PositiveTimestamp ( UNIX_EPOCH + Duration :: from_secs ( unix_seconds) ) )
769
780
}
770
781
}
771
782
772
783
/// 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
784
+ /// the Range `0...SYSTEM_TIME_MAX_UNIX_TIMESTAMP - MAX_EXPIRY_TIME `, otherwise return a
774
785
/// `CreationError::TimestampOutOfBounds`.
775
786
pub fn from_system_time ( time : SystemTime ) -> Result < Self , CreationError > {
776
787
if time
777
788
. duration_since ( UNIX_EPOCH )
778
- . map ( |t| t. as_secs ( ) <= SYSTEM_TIME_MAX_UNIX_TIMESTAMP ) // check for consistency reasons
789
+ . map ( |t| t. as_secs ( ) <= SYSTEM_TIME_MAX_UNIX_TIMESTAMP - MAX_EXPIRY_TIME )
779
790
. unwrap_or ( true )
780
791
{
781
792
Ok ( PositiveTimestamp ( time) )
@@ -1009,6 +1020,32 @@ impl Deref for PayeePubKey {
1009
1020
}
1010
1021
}
1011
1022
1023
+ impl ExpiryTime {
1024
+ pub fn from_seconds ( seconds : u64 ) -> Result < ExpiryTime , CreationError > {
1025
+ if seconds <= MAX_EXPIRY_TIME {
1026
+ Ok ( ExpiryTime ( Duration :: from_secs ( seconds) ) )
1027
+ } else {
1028
+ Err ( CreationError :: ExpiryTimeOutOfBounds )
1029
+ }
1030
+ }
1031
+
1032
+ pub fn from_duration ( duration : Duration ) -> Result < ExpiryTime , CreationError > {
1033
+ if duration. as_secs ( ) <= MAX_EXPIRY_TIME {
1034
+ Ok ( ExpiryTime ( duration) )
1035
+ } else {
1036
+ Err ( CreationError :: ExpiryTimeOutOfBounds )
1037
+ }
1038
+ }
1039
+
1040
+ pub fn as_seconds ( & self ) -> u64 {
1041
+ self . 0 . as_secs ( )
1042
+ }
1043
+
1044
+ pub fn as_duration ( & self ) -> & Duration {
1045
+ & self . 0
1046
+ }
1047
+ }
1048
+
1012
1049
impl Route {
1013
1050
pub fn new ( hops : Vec < RouteHop > ) -> Result < Route , CreationError > {
1014
1051
if hops. len ( ) <= 12 {
@@ -1064,6 +1101,9 @@ pub enum CreationError {
1064
1101
1065
1102
/// The unix timestamp of the supplied date is <0 or can't be represented as `SystemTime`
1066
1103
TimestampOutOfBounds ,
1104
+
1105
+ /// The supplied expiry time could cause an overflow if added to a `PositiveTimestamp`
1106
+ ExpiryTimeOutOfBounds ,
1067
1107
}
1068
1108
1069
1109
/// Errors that may occur when converting a `RawInvoice` to an `Invoice`. They relate to the
@@ -1109,7 +1149,27 @@ mod test {
1109
1149
let year = Duration :: from_secs ( 60 * 60 * 24 * 365 ) ;
1110
1150
1111
1151
// Make sure that the library will keep working for another year
1112
- assert ! ( fail_date. duration_since( SystemTime :: now( ) ) . unwrap( ) > year)
1152
+ assert ! ( fail_date. duration_since( SystemTime :: now( ) ) . unwrap( ) > year) ;
1153
+
1154
+ let max_ts = :: PositiveTimestamp :: from_unix_timestamp (
1155
+ :: SYSTEM_TIME_MAX_UNIX_TIMESTAMP - :: MAX_EXPIRY_TIME
1156
+ ) . unwrap ( ) ;
1157
+ let max_exp = :: ExpiryTime :: from_seconds ( :: MAX_EXPIRY_TIME ) . unwrap ( ) ;
1158
+
1159
+ assert_eq ! (
1160
+ ( * max_ts. as_time( ) + * max_exp. as_duration( ) ) . duration_since( UNIX_EPOCH ) . unwrap( ) . as_secs( ) ,
1161
+ :: SYSTEM_TIME_MAX_UNIX_TIMESTAMP
1162
+ ) ;
1163
+
1164
+ assert_eq ! (
1165
+ :: PositiveTimestamp :: from_unix_timestamp( :: SYSTEM_TIME_MAX_UNIX_TIMESTAMP + 1 ) ,
1166
+ Err ( :: CreationError :: TimestampOutOfBounds )
1167
+ ) ;
1168
+
1169
+ assert_eq ! (
1170
+ :: ExpiryTime :: from_seconds( :: MAX_EXPIRY_TIME + 1 ) ,
1171
+ Err ( :: CreationError :: ExpiryTimeOutOfBounds )
1172
+ ) ;
1113
1173
}
1114
1174
1115
1175
#[ test]
@@ -1297,7 +1357,7 @@ mod test {
1297
1357
use :: * ;
1298
1358
use secp256k1:: Secp256k1 ;
1299
1359
use secp256k1:: key:: { SecretKey , PublicKey } ;
1300
- use std:: time:: UNIX_EPOCH ;
1360
+ use std:: time:: { UNIX_EPOCH , Duration } ;
1301
1361
1302
1362
let secp_ctx = Secp256k1 :: new ( ) ;
1303
1363
@@ -1349,7 +1409,7 @@ mod test {
1349
1409
. amount_pico_btc ( 123 )
1350
1410
. timestamp_raw ( 1234567 )
1351
1411
. payee_pub_key ( public_key. clone ( ) )
1352
- . expiry_time_seconds ( 54321 )
1412
+ . expiry_time ( Duration :: from_secs ( 54321 ) )
1353
1413
. min_final_cltv_expiry ( 144 )
1354
1414
. min_final_cltv_expiry ( 143 )
1355
1415
. fallback ( Fallback :: PubKeyHash ( [ 0 ; 20 ] ) )
@@ -1372,7 +1432,7 @@ mod test {
1372
1432
1234567
1373
1433
) ;
1374
1434
assert_eq ! ( invoice. payee_pub_key( ) , Some ( & PayeePubKey ( public_key) ) ) ;
1375
- assert_eq ! ( invoice. expiry_time( ) , Some ( & ExpiryTime { seconds : 54321 } ) ) ;
1435
+ assert_eq ! ( invoice. expiry_time( ) , Some ( & ExpiryTime :: from_seconds ( 54321 ) . unwrap ( ) ) ) ;
1376
1436
assert_eq ! ( invoice. min_final_cltv_expiry( ) , Some ( & MinFinalCltvExpiry ( 144 ) ) ) ;
1377
1437
assert_eq ! ( invoice. fallbacks( ) , vec![ & Fallback :: PubKeyHash ( [ 0 ; 20 ] ) ] ) ;
1378
1438
assert_eq ! ( invoice. routes( ) , vec![ & Route ( route_1) , & Route ( route_2) ] ) ;
0 commit comments