Skip to content
This repository was archived by the owner on Apr 13, 2021. It is now read-only.

Commit 366f235

Browse files
committed
Use std::time::Duration instead of u64 for expiry time
1 parent 5b07fc1 commit 366f235

File tree

4 files changed

+86
-25
lines changed

4 files changed

+86
-25
lines changed

src/de.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -485,11 +485,11 @@ impl FromBase32 for ExpiryTime {
485485
type Err = ParseError;
486486

487487
fn from_base32(field_data: &[u5]) -> Result<ExpiryTime, ParseError> {
488-
let expiry = parse_int_be::<u64, u5>(field_data, 32);
489-
if let Some(expiry) = expiry {
490-
Ok(ExpiryTime{seconds: expiry})
491-
} else {
492-
Err(ParseError::IntegerOverflowError)
488+
match parse_int_be::<u64, u5>(field_data, 32)
489+
.and_then(|t| ExpiryTime::from_seconds(t).ok()) // ok, since the only error is out of bounds
490+
{
491+
Some(t) => Ok(t),
492+
None => Err(ParseError::IntegerOverflowError),
493493
}
494494
}
495495
}
@@ -814,7 +814,7 @@ mod test {
814814
use bech32::FromBase32;
815815

816816
let input = from_bech32("pu".as_bytes());
817-
let expected = Ok(ExpiryTime{seconds: 60});
817+
let expected = Ok(ExpiryTime::from_seconds(60).unwrap());
818818
assert_eq!(ExpiryTime::from_base32(&input), expected);
819819

820820
let input_too_large = from_bech32("sqqqqqqqqqqqq".as_bytes());

src/lib.rs

Lines changed: 77 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,15 @@ mod tb;
2222
pub use de::{ParseError, ParseOrSemanticError};
2323

2424

25-
// TODO: fix before 2038 (see rust PR #55527)
25+
// TODO: fix before 2037 (see rust PR #55527)
2626
/// Defines the maximum UNIX timestamp that can be represented as `SystemTime`. This is checked by
2727
/// one of the unit tests, please run them.
2828
const SYSTEM_TIME_MAX_UNIX_TIMESTAMP: u64 = std::i32::MAX as u64;
2929

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+
3034
/// This function is used as a static assert for the size of `SystemTime`. If the crate fails to
3135
/// compile due to it this indicates that your system uses unexpected bounds for `SystemTime`. You
3236
/// can remove this functions and run the test `test_system_time_bounds_assumptions`. In any case,
@@ -261,11 +265,15 @@ pub struct Description(String);
261265
#[derive(Eq, PartialEq, Debug, Clone)]
262266
pub struct PayeePubKey(pub PublicKey);
263267

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
265275
#[derive(Eq, PartialEq, Debug, Clone)]
266-
pub struct ExpiryTime {
267-
pub seconds: u64
268-
}
276+
pub struct ExpiryTime(Duration);
269277

270278
/// `min_final_cltv_expiry` to use for the last HTLC in the route
271279
#[derive(Eq, PartialEq, Debug, Clone)]
@@ -381,9 +389,12 @@ impl<D: tb::Bool, H: tb::Bool, T: tb::Bool> InvoiceBuilder<D, H, T> {
381389
self
382390
}
383391

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+
};
387398
self
388399
}
389400

@@ -490,7 +501,7 @@ impl<D: tb::Bool, H: tb::Bool> InvoiceBuilder<D, H, tb::False> {
490501

491502
/// Sets the timestamp to the current UNIX timestamp.
492503
pub fn current_timestamp(mut self) -> InvoiceBuilder<D, H, tb::True> {
493-
use std::time::{SystemTime, UNIX_EPOCH};
504+
use std::time::SystemTime;
494505
let now = PositiveTimestamp::from_system_time(SystemTime::now());
495506
self.timestamp = Some(now.expect("for the foreseeable future this shouldn't happen"));
496507
self.set_flags()
@@ -759,23 +770,23 @@ impl RawInvoice {
759770

760771
impl PositiveTimestamp {
761772
/// 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
763774
/// `CreationError::TimestampOutOfBounds`.
764775
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 {
766777
Err(CreationError::TimestampOutOfBounds)
767778
} else {
768779
Ok(PositiveTimestamp(UNIX_EPOCH + Duration::from_secs(unix_seconds)))
769780
}
770781
}
771782

772783
/// 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
774785
/// `CreationError::TimestampOutOfBounds`.
775786
pub fn from_system_time(time: SystemTime) -> Result<Self, CreationError> {
776787
if time
777788
.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)
779790
.unwrap_or(true)
780791
{
781792
Ok(PositiveTimestamp(time))
@@ -1009,6 +1020,32 @@ impl Deref for PayeePubKey {
10091020
}
10101021
}
10111022

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+
10121049
impl Route {
10131050
pub fn new(hops: Vec<RouteHop>) -> Result<Route, CreationError> {
10141051
if hops.len() <= 12 {
@@ -1064,6 +1101,9 @@ pub enum CreationError {
10641101

10651102
/// The unix timestamp of the supplied date is <0 or can't be represented as `SystemTime`
10661103
TimestampOutOfBounds,
1104+
1105+
/// The supplied expiry time could cause an overflow if added to a `PositiveTimestamp`
1106+
ExpiryTimeOutOfBounds,
10671107
}
10681108

10691109
/// Errors that may occur when converting a `RawInvoice` to an `Invoice`. They relate to the
@@ -1109,7 +1149,27 @@ mod test {
11091149
let year = Duration::from_secs(60 * 60 * 24 * 365);
11101150

11111151
// 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+
);
11131173
}
11141174

11151175
#[test]
@@ -1297,7 +1357,7 @@ mod test {
12971357
use ::*;
12981358
use secp256k1::Secp256k1;
12991359
use secp256k1::key::{SecretKey, PublicKey};
1300-
use std::time::UNIX_EPOCH;
1360+
use std::time::{UNIX_EPOCH, Duration};
13011361

13021362
let secp_ctx = Secp256k1::new();
13031363

@@ -1349,7 +1409,7 @@ mod test {
13491409
.amount_pico_btc(123)
13501410
.timestamp_raw(1234567)
13511411
.payee_pub_key(public_key.clone())
1352-
.expiry_time_seconds(54321)
1412+
.expiry_time(Duration::from_secs(54321))
13531413
.min_final_cltv_expiry(144)
13541414
.min_final_cltv_expiry(143)
13551415
.fallback(Fallback::PubKeyHash([0;20]))
@@ -1372,7 +1432,7 @@ mod test {
13721432
1234567
13731433
);
13741434
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()));
13761436
assert_eq!(invoice.min_final_cltv_expiry(), Some(&MinFinalCltvExpiry(144)));
13771437
assert_eq!(invoice.fallbacks(), vec![&Fallback::PubKeyHash([0;20])]);
13781438
assert_eq!(invoice.routes(), vec![&Route(route_1), &Route(route_2)]);

src/ser.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ impl ToBase32<Vec<u5>> for PayeePubKey {
169169

170170
impl ToBase32<Vec<u5>> for ExpiryTime {
171171
fn to_base32(&self) -> Vec<u5> {
172-
encode_int_be_base32(self.seconds)
172+
encode_int_be_base32(self.as_seconds())
173173
}
174174
}
175175

tests/ser_de.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use bitcoin_hashes::hex::FromHex;
66
use bitcoin_hashes::sha256::Sha256Hash;
77
use lightning_invoice::*;
88
use secp256k1::{Secp256k1, RecoverableSignature, RecoveryId};
9+
use std::time::Duration;
910

1011
// TODO: add more of the examples from BOLT11 and generate ones causing SemanticErrors
1112

@@ -50,7 +51,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawInvoice, Option<SemanticError>)> {
5051
"0001020304050607080900010203040506070809000102030405060708090102"
5152
).unwrap())
5253
.description("1 cup coffee".to_owned())
53-
.expiry_time_seconds(60)
54+
.expiry_time(Duration::from_secs(60))
5455
.build_raw()
5556
.unwrap()
5657
.sign(|_| {

0 commit comments

Comments
 (0)