X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning-invoice%2Fsrc%2Flib.rs;h=4737173eef5da6e1f0ebdf16d8d235b536db4dce;hb=fc9a4c22d195a75ad5942eed271757f285452214;hp=defe958a1c05406cf8eb6cdfe409c9958c26458b;hpb=8b3516208a6465ae8a5f1a987a5d1fd2585527a7;p=rust-lightning diff --git a/lightning-invoice/src/lib.rs b/lightning-invoice/src/lib.rs index defe958a..4737173e 100644 --- a/lightning-invoice/src/lib.rs +++ b/lightning-invoice/src/lib.rs @@ -86,7 +86,7 @@ mod prelude { pub use alloc::string::ToString; } -use prelude::*; +use crate::prelude::*; /// Sync compat for std/no_std #[cfg(feature = "std")] @@ -101,7 +101,7 @@ mod sync; /// Errors that indicate what is wrong with the invoice. They have some granularity for debug /// reasons, but should generally result in an "invalid BOLT11 invoice" message for the user. #[allow(missing_docs)] -#[derive(PartialEq, Debug, Clone)] +#[derive(PartialEq, Eq, Debug, Clone)] pub enum ParseError { Bech32Error(bech32::Error), ParseAmountError(ParseIntError), @@ -129,13 +129,13 @@ pub enum ParseError { /// Indicates that something went wrong while parsing or validating the invoice. Parsing errors /// should be mostly seen as opaque and are only there for debugging reasons. Semantic errors /// like wrong signatures, missing fields etc. could mean that someone tampered with the invoice. -#[derive(PartialEq, Debug, Clone)] +#[derive(PartialEq, Eq, Debug, Clone)] pub enum ParseOrSemanticError { /// The invoice couldn't be decoded ParseError(ParseError), /// The invoice could be decoded but violates the BOLT11 standard - SemanticError(::SemanticError), + SemanticError(crate::SemanticError), } /// The number of bits used to represent timestamps as defined in BOLT 11. @@ -360,7 +360,7 @@ impl SiPrefix { /// (C-not exported) As we don't yet support a slice of enums, and also because this function /// isn't the most critical to expose. pub fn values_desc() -> &'static [SiPrefix] { - use SiPrefix::*; + use crate::SiPrefix::*; static VALUES: [SiPrefix; 4] = [Milli, Micro, Nano, Pico]; &VALUES } @@ -540,7 +540,8 @@ impl InvoiceBui self } - /// Sets the expiry time + /// Sets the expiry time, dropping the subsecond part (which is not representable in BOLT 11 + /// invoices). pub fn expiry_time(mut self, expiry_time: Duration) -> Self { self.tagged_fields.push(TaggedField::ExpiryTime(ExpiryTime::from_duration(expiry_time))); self @@ -632,7 +633,8 @@ impl InvoiceBuilder InvoiceBuilder { match PositiveTimestamp::from_duration_since_epoch(time) { Ok(t) => self.timestamp = Some(t), @@ -960,12 +962,18 @@ impl PositiveTimestamp { /// /// Otherwise, returns a [`CreationError::TimestampOutOfBounds`]. pub fn from_unix_timestamp(unix_seconds: u64) -> Result { - Self::from_duration_since_epoch(Duration::from_secs(unix_seconds)) + if unix_seconds <= MAX_TIMESTAMP { + Ok(Self(Duration::from_secs(unix_seconds))) + } else { + Err(CreationError::TimestampOutOfBounds) + } } /// Creates a `PositiveTimestamp` from a [`SystemTime`] with a corresponding Unix timestamp in /// the range `0..=MAX_TIMESTAMP`. /// + /// Note that the subsecond part is dropped as it is not representable in BOLT 11 invoices. + /// /// Otherwise, returns a [`CreationError::TimestampOutOfBounds`]. #[cfg(feature = "std")] pub fn from_system_time(time: SystemTime) -> Result { @@ -977,13 +985,11 @@ impl PositiveTimestamp { /// Creates a `PositiveTimestamp` from a [`Duration`] since the Unix epoch in the range /// `0..=MAX_TIMESTAMP`. /// + /// Note that the subsecond part is dropped as it is not representable in BOLT 11 invoices. + /// /// Otherwise, returns a [`CreationError::TimestampOutOfBounds`]. pub fn from_duration_since_epoch(duration: Duration) -> Result { - if duration.as_secs() <= MAX_TIMESTAMP { - Ok(PositiveTimestamp(duration)) - } else { - Err(CreationError::TimestampOutOfBounds) - } + Self::from_unix_timestamp(duration.as_secs()) } /// Returns the Unix timestamp representing the stored time @@ -1356,9 +1362,9 @@ impl ExpiryTime { ExpiryTime(Duration::from_secs(seconds)) } - /// Construct an `ExpiryTime` from a `Duration`. + /// Construct an `ExpiryTime` from a `Duration`, dropping the sub-second part. pub fn from_duration(duration: Duration) -> ExpiryTime { - ExpiryTime(duration) + Self::from_seconds(duration.as_secs()) } /// Returns the expiry time in seconds @@ -1556,15 +1562,15 @@ mod test { #[test] fn test_system_time_bounds_assumptions() { assert_eq!( - ::PositiveTimestamp::from_unix_timestamp(::MAX_TIMESTAMP + 1), - Err(::CreationError::TimestampOutOfBounds) + crate::PositiveTimestamp::from_unix_timestamp(crate::MAX_TIMESTAMP + 1), + Err(crate::CreationError::TimestampOutOfBounds) ); } #[test] fn test_calc_invoice_hash() { - use ::{RawInvoice, RawHrp, RawDataPart, Currency, PositiveTimestamp}; - use ::TaggedField::*; + use crate::{RawInvoice, RawHrp, RawDataPart, Currency, PositiveTimestamp}; + use crate::TaggedField::*; let invoice = RawInvoice { hrp: RawHrp { @@ -1575,10 +1581,10 @@ mod test { data: RawDataPart { timestamp: PositiveTimestamp::from_unix_timestamp(1496314658).unwrap(), tagged_fields: vec![ - PaymentHash(::Sha256(sha256::Hash::from_hex( + PaymentHash(crate::Sha256(sha256::Hash::from_hex( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap())).into(), - Description(::Description::new( + Description(crate::Description::new( "Please consider supporting this project".to_owned() ).unwrap()).into(), ], @@ -1596,11 +1602,11 @@ mod test { #[test] fn test_check_signature() { - use TaggedField::*; + use crate::TaggedField::*; use secp256k1::Secp256k1; use secp256k1::ecdsa::{RecoveryId, RecoverableSignature}; use secp256k1::{SecretKey, PublicKey}; - use {SignedRawInvoice, InvoiceSignature, RawInvoice, RawHrp, RawDataPart, Currency, Sha256, + use crate::{SignedRawInvoice, InvoiceSignature, RawInvoice, RawHrp, RawDataPart, Currency, Sha256, PositiveTimestamp}; let invoice = SignedRawInvoice { @@ -1617,7 +1623,7 @@ mod test { "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap())).into(), Description( - ::Description::new( + crate::Description::new( "Please consider supporting this project".to_owned() ).unwrap() ).into(), @@ -1653,7 +1659,7 @@ mod test { ).unwrap(); let public_key = PublicKey::from_secret_key(&Secp256k1::new(), &private_key); - assert_eq!(invoice.recover_payee_pub_key(), Ok(::PayeePubKey(public_key))); + assert_eq!(invoice.recover_payee_pub_key(), Ok(crate::PayeePubKey(public_key))); let (raw_invoice, _, _) = invoice.into_parts(); let new_signed = raw_invoice.sign::<_, ()>(|hash| { @@ -1665,11 +1671,11 @@ mod test { #[test] fn test_check_feature_bits() { - use TaggedField::*; + use crate::TaggedField::*; use lightning::ln::features::InvoiceFeatures; use secp256k1::Secp256k1; use secp256k1::SecretKey; - use {RawInvoice, RawHrp, RawDataPart, Currency, Sha256, PositiveTimestamp, Invoice, + use crate::{RawInvoice, RawHrp, RawDataPart, Currency, Sha256, PositiveTimestamp, Invoice, SemanticError}; let private_key = SecretKey::from_slice(&[42; 32]).unwrap(); @@ -1687,7 +1693,7 @@ mod test { "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap())).into(), Description( - ::Description::new( + crate::Description::new( "Please consider supporting this project".to_owned() ).unwrap() ).into(), @@ -1712,11 +1718,14 @@ mod test { }.unwrap(); assert_eq!(Invoice::from_signed(invoice), Err(SemanticError::InvalidFeatures)); + let mut payment_secret_features = InvoiceFeatures::empty(); + payment_secret_features.set_payment_secret_required(); + // Including payment secret and feature bits let invoice = { let mut invoice = invoice_template.clone(); invoice.data.tagged_fields.push(PaymentSecret(payment_secret).into()); - invoice.data.tagged_fields.push(Features(InvoiceFeatures::known()).into()); + invoice.data.tagged_fields.push(Features(payment_secret_features.clone()).into()); invoice.sign::<_, ()>(|hash| Ok(Secp256k1::new().sign_ecdsa_recoverable(hash, &private_key))) }.unwrap(); assert!(Invoice::from_signed(invoice).is_ok()); @@ -1739,7 +1748,7 @@ mod test { // Missing payment secret let invoice = { let mut invoice = invoice_template.clone(); - invoice.data.tagged_fields.push(Features(InvoiceFeatures::known()).into()); + invoice.data.tagged_fields.push(Features(payment_secret_features).into()); invoice.sign::<_, ()>(|hash| Ok(Secp256k1::new().sign_ecdsa_recoverable(hash, &private_key))) }.unwrap(); assert_eq!(Invoice::from_signed(invoice), Err(SemanticError::NoPaymentSecret)); @@ -1756,7 +1765,7 @@ mod test { #[test] fn test_builder_amount() { - use ::*; + use crate::*; let builder = InvoiceBuilder::new(Currency::Bitcoin) .description("Test".into()) @@ -1783,7 +1792,7 @@ mod test { #[test] fn test_builder_fail() { - use ::*; + use crate::*; use lightning::routing::router::RouteHintHop; use std::iter::FromIterator; use secp256k1::PublicKey; @@ -1837,7 +1846,7 @@ mod test { #[test] fn test_builder_ok() { - use ::*; + use crate::*; use lightning::routing::router::RouteHintHop; use secp256k1::Secp256k1; use secp256k1::{SecretKey, PublicKey}; @@ -1944,7 +1953,12 @@ mod test { ); assert_eq!(invoice.payment_hash(), &sha256::Hash::from_slice(&[21;32][..]).unwrap()); assert_eq!(invoice.payment_secret(), &PaymentSecret([42; 32])); - assert_eq!(invoice.features(), Some(&InvoiceFeatures::known())); + + let mut expected_features = InvoiceFeatures::empty(); + expected_features.set_variable_length_onion_required(); + expected_features.set_payment_secret_required(); + expected_features.set_basic_mpp_optional(); + assert_eq!(invoice.features(), Some(&expected_features)); let raw_invoice = builder.build_raw().unwrap(); assert_eq!(raw_invoice, *invoice.into_signed_raw().raw_invoice()) @@ -1952,7 +1966,7 @@ mod test { #[test] fn test_default_values() { - use ::*; + use crate::*; use secp256k1::Secp256k1; use secp256k1::SecretKey; @@ -1978,7 +1992,7 @@ mod test { #[test] fn test_expiration() { - use ::*; + use crate::*; use secp256k1::Secp256k1; use secp256k1::SecretKey;