Merge pull request #1649 from dunxen/2022-08-implementnodefailure
[rust-lightning] / lightning-invoice / src / lib.rs
index bad024c66c5a37114d144c146014294e0f747501..9457d8ae6cc1ccf206df675030c9fc5b57a31160 100644 (file)
@@ -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))]
 
@@ -236,7 +239,7 @@ pub struct InvoiceBuilder<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S:
 ///  1. using `InvoiceBuilder`
 ///  2. using `Invoice::from_signed(SignedRawInvoice)`
 ///  3. using `str::parse::<Invoice>(&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<Enum>
-#[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,
@@ -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<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> 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<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, tb
                self.set_flags()
        }
 
-       /// Sets the timestamp to a duration since the Unix epoch.
+       /// Sets the timestamp to a duration since the Unix epoch, dropping the subsecond part (which
+       /// is not representable in BOLT 11 invoices).
        pub fn duration_since_epoch(mut self, time: Duration) -> InvoiceBuilder<D, H, tb::True, C, S> {
                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<F, E>(self, sign_method: F) -> Result<SignedRawInvoice, E>
                where F: FnOnce(&Message) -> Result<RecoverableSignature, E>
        {
-               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, CreationError> {
-               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<Self, CreationError> {
@@ -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<Self, CreationError> {
-               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
@@ -1588,7 +1597,7 @@ mod test {
                        0xd5, 0x18, 0xe1, 0xc9
                ];
 
-               assert_eq!(invoice.hash(), expected_hash)
+               assert_eq!(invoice.signable_hash(), expected_hash)
        }
 
        #[test]
@@ -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));
@@ -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())