X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning-invoice%2Fsrc%2Flib.rs;h=4737173eef5da6e1f0ebdf16d8d235b536db4dce;hb=fc9a4c22d195a75ad5942eed271757f285452214;hp=bad024c66c5a37114d144c146014294e0f747501;hpb=b76040718f954fa7baae42c7070e3ff6ac8add3c;p=rust-lightning diff --git a/lightning-invoice/src/lib.rs b/lightning-invoice/src/lib.rs index bad024c6..4737173e 100644 --- a/lightning-invoice/src/lib.rs +++ b/lightning-invoice/src/lib.rs @@ -1,9 +1,12 @@ +// Prefix these with `rustdoc::` when we update our MSRV to be >= 1.52 to remove warnings. +#![deny(broken_intra_doc_links)] +#![deny(private_intra_doc_links)] + #![deny(missing_docs)] #![deny(non_upper_case_globals)] #![deny(non_camel_case_types)] #![deny(non_snake_case)] #![deny(unused_mut)] -#![deny(broken_intra_doc_links)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] @@ -83,7 +86,7 @@ mod prelude { pub use alloc::string::ToString; } -use prelude::*; +use crate::prelude::*; /// Sync compat for std/no_std #[cfg(feature = "std")] @@ -98,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), @@ -126,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. @@ -236,7 +239,7 @@ pub struct InvoiceBuilder(&str)` -#[derive(Eq, PartialEq, Debug, Clone)] +#[derive(Eq, PartialEq, Debug, Clone, Hash)] pub struct Invoice { signed_invoice: SignedRawInvoice, } @@ -260,7 +263,7 @@ pub enum InvoiceDescription<'f> { /// /// # Invariants /// The hash has to be either from the deserialized invoice or from the serialized `raw_invoice`. -#[derive(Eq, PartialEq, Debug, Clone)] +#[derive(Eq, PartialEq, Debug, Clone, Hash)] pub struct SignedRawInvoice { /// The rawInvoice that the signature belongs to raw_invoice: RawInvoice, @@ -283,7 +286,7 @@ pub struct SignedRawInvoice { /// De- and encoding should not lead to information loss but may lead to different hashes. /// /// For methods without docs see the corresponding methods in `Invoice`. -#[derive(Eq, PartialEq, Debug, Clone)] +#[derive(Eq, PartialEq, Debug, Clone, Hash)] pub struct RawInvoice { /// human readable part pub hrp: RawHrp, @@ -295,7 +298,7 @@ pub struct RawInvoice { /// Data of the `RawInvoice` that is encoded in the human readable part /// /// (C-not exported) As we don't yet support Option -#[derive(Eq, PartialEq, Debug, Clone)] +#[derive(Eq, PartialEq, Debug, Clone, Hash)] pub struct RawHrp { /// The currency deferred from the 3rd and 4th character of the bech32 transaction pub currency: Currency, @@ -308,7 +311,7 @@ pub struct RawHrp { } /// Data of the `RawInvoice` that is encoded in the data part -#[derive(Eq, PartialEq, Debug, Clone)] +#[derive(Eq, PartialEq, Debug, Clone, Hash)] pub struct RawDataPart { /// generation time of the invoice pub timestamp: PositiveTimestamp, @@ -323,11 +326,11 @@ pub struct RawDataPart { /// /// The Unix timestamp representing the stored time has to be positive and no greater than /// [`MAX_TIMESTAMP`]. -#[derive(Eq, PartialEq, Debug, Clone)] +#[derive(Eq, PartialEq, Debug, Clone, Hash)] pub struct PositiveTimestamp(Duration); /// SI prefixes for the human readable part -#[derive(Eq, PartialEq, Debug, Clone, Copy)] +#[derive(Eq, PartialEq, Debug, Clone, Copy, Hash)] pub enum SiPrefix { /// 10^-3 Milli, @@ -357,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 } @@ -453,7 +456,7 @@ pub enum Fallback { } /// Recoverable signature -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Hash, Eq, PartialEq)] pub struct InvoiceSignature(pub RecoverableSignature); /// Private routing information @@ -537,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 @@ -629,7 +633,8 @@ impl InvoiceBuilder InvoiceBuilder { match PositiveTimestamp::from_duration_since_epoch(time) { Ok(t) => self.timestamp = Some(t), @@ -742,7 +747,7 @@ impl SignedRawInvoice { } /// The hash of the `RawInvoice` that was signed. - pub fn hash(&self) -> &[u8; 32] { + pub fn signable_hash(&self) -> &[u8; 32] { &self.hash } @@ -850,8 +855,8 @@ impl RawInvoice { hash } - /// Calculate the hash of the encoded `RawInvoice` - pub fn hash(&self) -> [u8; 32] { + /// Calculate the hash of the encoded `RawInvoice` which should be signed. + pub fn signable_hash(&self) -> [u8; 32] { use bech32::ToBase32; RawInvoice::hash_from_parts( @@ -869,7 +874,7 @@ impl RawInvoice { pub fn sign(self, sign_method: F) -> Result where F: FnOnce(&Message) -> Result { - let raw_hash = self.hash(); + let raw_hash = self.signable_hash(); let hash = Message::from_slice(&raw_hash[..]) .expect("Hash is 32 bytes long, same as MESSAGE_SIZE"); let signature = sign_method(&hash)?; @@ -957,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 { @@ -974,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 @@ -1353,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 @@ -1553,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 { @@ -1572,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(), ], @@ -1588,16 +1597,16 @@ mod test { 0xd5, 0x18, 0xe1, 0xc9 ]; - assert_eq!(invoice.hash(), expected_hash) + assert_eq!(invoice.signable_hash(), expected_hash) } #[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 { @@ -1614,7 +1623,7 @@ mod test { "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap())).into(), Description( - ::Description::new( + crate::Description::new( "Please consider supporting this project".to_owned() ).unwrap() ).into(), @@ -1650,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| { @@ -1662,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(); @@ -1684,7 +1693,7 @@ mod test { "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap())).into(), Description( - ::Description::new( + crate::Description::new( "Please consider supporting this project".to_owned() ).unwrap() ).into(), @@ -1709,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()); @@ -1736,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)); @@ -1753,7 +1765,7 @@ mod test { #[test] fn test_builder_amount() { - use ::*; + use crate::*; let builder = InvoiceBuilder::new(Currency::Bitcoin) .description("Test".into()) @@ -1780,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; @@ -1834,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}; @@ -1941,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()) @@ -1949,7 +1966,7 @@ mod test { #[test] fn test_default_values() { - use ::*; + use crate::*; use secp256k1::Secp256k1; use secp256k1::SecretKey; @@ -1975,7 +1992,7 @@ mod test { #[test] fn test_expiration() { - use ::*; + use crate::*; use secp256k1::Secp256k1; use secp256k1::SecretKey;