X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning-invoice%2Fsrc%2Flib.rs;h=f53c8953b47c66b9344d86a928d9e834790989f7;hb=71af4a2d1553950adadcb3c4e69446f4d276a62c;hp=defe958a1c05406cf8eb6cdfe409c9958c26458b;hpb=8b3516208a6465ae8a5f1a987a5d1fd2585527a7;p=rust-lightning diff --git a/lightning-invoice/src/lib.rs b/lightning-invoice/src/lib.rs index defe958a..f53c8953 100644 --- a/lightning-invoice/src/lib.rs +++ b/lightning-invoice/src/lib.rs @@ -18,9 +18,11 @@ //! invoices and functions to create, encode and decode these. If you just want to use the standard //! en-/decoding functionality this should get you started: //! -//! * For parsing use `str::parse::(&self)` (see the docs of `impl FromStr for Invoice`) -//! * For constructing invoices use the `InvoiceBuilder` -//! * For serializing invoices use the `Display`/`ToString` traits +//! * For parsing use `str::parse::(&self)` (see [`Invoice::from_str`]) +//! * For constructing invoices use the [`InvoiceBuilder`] +//! * For serializing invoices use the [`Display`]/[`ToString`] traits +//! +//! [`Invoice::from_str`]: crate::Invoice#impl-FromStr #[cfg(not(any(feature = "std", feature = "no-std")))] compile_error!("at least one of the `std` or `no-std` features must be enabled"); @@ -45,8 +47,9 @@ extern crate serde; use std::time::SystemTime; use bech32::u5; -use bitcoin_hashes::Hash; -use bitcoin_hashes::sha256; +use bitcoin::{Address, Network, PubkeyHash, ScriptHash}; +use bitcoin::util::address::{Payload, WitnessVersion}; +use bitcoin_hashes::{Hash, sha256}; use lightning::ln::PaymentSecret; use lightning::ln::features::InvoiceFeatures; #[cfg(any(doc, test))] @@ -86,7 +89,7 @@ mod prelude { pub use alloc::string::ToString; } -use prelude::*; +use crate::prelude::*; /// Sync compat for std/no_std #[cfg(feature = "std")] @@ -101,7 +104,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 +132,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. @@ -154,13 +157,13 @@ pub const DEFAULT_EXPIRY_TIME: u64 = 3600; /// Default minimum final CLTV expiry as defined by [BOLT 11]. /// /// Note that this is *not* the same value as rust-lightning's minimum CLTV expiry, which is -/// provided in [`MIN_FINAL_CLTV_EXPIRY`]. +/// provided in [`MIN_FINAL_CLTV_EXPIRY_DELTA`]. /// /// [BOLT 11]: https://github.com/lightning/bolts/blob/master/11-payment-encoding.md -/// [`MIN_FINAL_CLTV_EXPIRY`]: lightning::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY -pub const DEFAULT_MIN_FINAL_CLTV_EXPIRY: u64 = 18; +/// [`MIN_FINAL_CLTV_EXPIRY_DELTA`]: lightning::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA +pub const DEFAULT_MIN_FINAL_CLTV_EXPIRY_DELTA: u64 = 18; -/// Builder for `Invoice`s. It's the most convenient and advised way to use this library. It ensures +/// Builder for [`Invoice`]s. It's the most convenient and advised way to use this library. It ensures /// that only a semantically and syntactically correct Invoice can be built using it. /// /// ``` @@ -199,7 +202,7 @@ pub const DEFAULT_MIN_FINAL_CLTV_EXPIRY: u64 = 18; /// .payment_hash(payment_hash) /// .payment_secret(payment_secret) /// .current_timestamp() -/// .min_final_cltv_expiry(144) +/// .min_final_cltv_expiry_delta(144) /// .build_signed(|hash| { /// Secp256k1::new().sign_ecdsa_recoverable(hash, &private_key) /// }) @@ -212,13 +215,16 @@ pub const DEFAULT_MIN_FINAL_CLTV_EXPIRY: u64 = 18; /// # Type parameters /// The two parameters `D` and `H` signal if the builder already contains the correct amount of the /// given field: -/// * `D`: exactly one `Description` or `DescriptionHash` -/// * `H`: exactly one `PaymentHash` +/// * `D`: exactly one [`TaggedField::Description`] or [`TaggedField::DescriptionHash`] +/// * `H`: exactly one [`TaggedField::PaymentHash`] /// * `T`: the timestamp is set +/// * `C`: the CLTV expiry is set +/// * `S`: the payment secret is set +/// * `M`: payment metadata is set /// -/// (C-not exported) as we likely need to manually select one set of boolean type parameters. +/// This is not exported to bindings users as we likely need to manually select one set of boolean type parameters. #[derive(Eq, PartialEq, Debug, Clone)] -pub struct InvoiceBuilder { +pub struct InvoiceBuilder { currency: Currency, amount: Option, si_prefix: Option, @@ -231,14 +237,17 @@ pub struct InvoiceBuilder, phantom_c: core::marker::PhantomData, phantom_s: core::marker::PhantomData, + phantom_m: core::marker::PhantomData, } /// Represents a syntactically and semantically correct lightning BOLT11 invoice. /// /// There are three ways to construct an `Invoice`: -/// 1. using `InvoiceBuilder` -/// 2. using `Invoice::from_signed(SignedRawInvoice)` -/// 3. using `str::parse::(&str)` +/// 1. using [`InvoiceBuilder`] +/// 2. using [`Invoice::from_signed`] +/// 3. using `str::parse::(&str)` (see [`Invoice::from_str`]) +/// +/// [`Invoice::from_str`]: crate::Invoice#impl-FromStr #[derive(Eq, PartialEq, Debug, Clone, Hash)] pub struct Invoice { signed_invoice: SignedRawInvoice, @@ -247,7 +256,7 @@ pub struct Invoice { /// Represents the description of an invoice which has to be either a directly included string or /// a hash of a description provided out of band. /// -/// (C-not exported) As we don't have a good way to map the reference lifetimes making this +/// This is not exported to bindings users as we don't have a good way to map the reference lifetimes making this /// practically impossible to use safely in languages like C. #[derive(Eq, PartialEq, Debug, Clone)] pub enum InvoiceDescription<'f> { @@ -258,34 +267,34 @@ pub enum InvoiceDescription<'f> { Hash(&'f Sha256), } -/// Represents a signed `RawInvoice` with cached hash. The signature is not checked and may be +/// Represents a signed [`RawInvoice`] with cached hash. The signature is not checked and may be /// invalid. /// /// # Invariants -/// The hash has to be either from the deserialized invoice or from the serialized `raw_invoice`. +/// The hash has to be either from the deserialized invoice or from the serialized [`RawInvoice`]. #[derive(Eq, PartialEq, Debug, Clone, Hash)] pub struct SignedRawInvoice { /// The rawInvoice that the signature belongs to raw_invoice: RawInvoice, - /// Hash of the `RawInvoice` that will be used to check the signature. + /// Hash of the [`RawInvoice`] that will be used to check the signature. /// /// * if the `SignedRawInvoice` was deserialized the hash is of from the original encoded form, /// since it's not guaranteed that encoding it again will lead to the same result since integers /// could have been encoded with leading zeroes etc. /// * if the `SignedRawInvoice` was constructed manually the hash will be the calculated hash - /// from the `RawInvoice` + /// from the [`RawInvoice`] hash: [u8; 32], /// signature of the payment request signature: InvoiceSignature, } -/// Represents an syntactically correct Invoice for a payment on the lightning network, +/// Represents an syntactically correct [`Invoice`] for a payment on the lightning network, /// but without the signature information. -/// De- and encoding should not lead to information loss but may lead to different hashes. +/// Decoding and encoding should not lead to information loss but may lead to different hashes. /// -/// For methods without docs see the corresponding methods in `Invoice`. +/// For methods without docs see the corresponding methods in [`Invoice`]. #[derive(Eq, PartialEq, Debug, Clone, Hash)] pub struct RawInvoice { /// human readable part @@ -295,9 +304,9 @@ pub struct RawInvoice { pub data: RawDataPart, } -/// Data of the `RawInvoice` that is encoded in the human readable part +/// Data of the [`RawInvoice`] that is encoded in the human readable part. /// -/// (C-not exported) As we don't yet support Option +/// This is not exported to bindings users as we don't yet support `Option` #[derive(Eq, PartialEq, Debug, Clone, Hash)] pub struct RawHrp { /// The currency deferred from the 3rd and 4th character of the bech32 transaction @@ -310,7 +319,7 @@ pub struct RawHrp { pub si_prefix: Option, } -/// Data of the `RawInvoice` that is encoded in the data part +/// Data of the [`RawInvoice`] that is encoded in the data part #[derive(Eq, PartialEq, Debug, Clone, Hash)] pub struct RawDataPart { /// generation time of the invoice @@ -357,10 +366,10 @@ impl SiPrefix { /// Returns all enum variants of `SiPrefix` sorted in descending order of their associated /// multiplier. /// - /// (C-not exported) As we don't yet support a slice of enums, and also because this function + /// This is not exported to bindings users 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 } @@ -385,9 +394,32 @@ pub enum Currency { Signet, } +impl From for Currency { + fn from(network: Network) -> Self { + match network { + Network::Bitcoin => Currency::Bitcoin, + Network::Testnet => Currency::BitcoinTestnet, + Network::Regtest => Currency::Regtest, + Network::Signet => Currency::Signet, + } + } +} + +impl From for Network { + fn from(currency: Currency) -> Self { + match currency { + Currency::Bitcoin => Network::Bitcoin, + Currency::BitcoinTestnet => Network::Testnet, + Currency::Regtest => Network::Regtest, + Currency::Simnet => Network::Regtest, + Currency::Signet => Network::Signet, + } + } +} + /// Tagged field which may have an unknown tag /// -/// (C-not exported) as we don't currently support TaggedField +/// This is not exported to bindings users as we don't currently support TaggedField #[derive(Clone, Debug, Hash, Eq, PartialEq)] pub enum RawTaggedField { /// Parsed tagged field with known tag @@ -400,7 +432,7 @@ pub enum RawTaggedField { /// /// For descriptions of the enum values please refer to the enclosed type's docs. /// -/// (C-not exported) As we don't yet support enum variants with the same name the struct contained +/// This is not exported to bindings users as we don't yet support enum variants with the same name the struct contained /// in the variant. #[allow(missing_docs)] #[derive(Clone, Debug, Hash, Eq, PartialEq)] @@ -410,18 +442,28 @@ pub enum TaggedField { PayeePubKey(PayeePubKey), DescriptionHash(Sha256), ExpiryTime(ExpiryTime), - MinFinalCltvExpiry(MinFinalCltvExpiry), + MinFinalCltvExpiryDelta(MinFinalCltvExpiryDelta), Fallback(Fallback), PrivateRoute(PrivateRoute), PaymentSecret(PaymentSecret), + PaymentMetadata(Vec), Features(InvoiceFeatures), } /// SHA-256 hash #[derive(Clone, Debug, Hash, Eq, PartialEq)] -pub struct Sha256(/// (C-not exported) as the native hash types are not currently mapped +pub struct Sha256(/// This is not exported to bindings users as the native hash types are not currently mapped pub sha256::Hash); +impl Sha256 { + /// Constructs a new [`Sha256`] from the given bytes, which are assumed to be the output of a + /// single sha256 hash. + #[cfg(c_bindings)] + pub fn from_bytes(bytes: &[u8; 32]) -> Self { + Self(sha256::Hash::from_slice(bytes).expect("from_slice only fails if len is not 32")) + } +} + /// Description string /// /// # Invariants @@ -438,21 +480,20 @@ pub struct PayeePubKey(pub PublicKey); #[derive(Clone, Debug, Hash, Eq, PartialEq)] pub struct ExpiryTime(Duration); -/// `min_final_cltv_expiry` to use for the last HTLC in the route +/// `min_final_cltv_expiry_delta` to use for the last HTLC in the route #[derive(Clone, Debug, Hash, Eq, PartialEq)] -pub struct MinFinalCltvExpiry(pub u64); +pub struct MinFinalCltvExpiryDelta(pub u64); -// TODO: better types instead onf byte arrays /// Fallback address in case no LN payment is possible #[allow(missing_docs)] #[derive(Clone, Debug, Hash, Eq, PartialEq)] pub enum Fallback { SegWitProgram { - version: u5, + version: WitnessVersion, program: Vec, }, - PubKeyHash([u8; 20]), - ScriptHash([u8; 20]), + PubKeyHash(PubkeyHash), + ScriptHash(ScriptHash), } /// Recoverable signature @@ -475,19 +516,20 @@ pub mod constants { pub const TAG_PAYEE_PUB_KEY: u8 = 19; pub const TAG_DESCRIPTION_HASH: u8 = 23; pub const TAG_EXPIRY_TIME: u8 = 6; - pub const TAG_MIN_FINAL_CLTV_EXPIRY: u8 = 24; + pub const TAG_MIN_FINAL_CLTV_EXPIRY_DELTA: u8 = 24; pub const TAG_FALLBACK: u8 = 9; pub const TAG_PRIVATE_ROUTE: u8 = 3; pub const TAG_PAYMENT_SECRET: u8 = 16; + pub const TAG_PAYMENT_METADATA: u8 = 27; pub const TAG_FEATURES: u8 = 5; } -impl InvoiceBuilder { +impl InvoiceBuilder { /// Construct new, empty `InvoiceBuilder`. All necessary fields have to be filled first before /// `InvoiceBuilder::build(self)` becomes available. - pub fn new(currrency: Currency) -> Self { + pub fn new(currency: Currency) -> Self { InvoiceBuilder { - currency: currrency, + currency, amount: None, si_prefix: None, timestamp: None, @@ -499,14 +541,15 @@ impl InvoiceBuilder { phantom_t: core::marker::PhantomData, phantom_c: core::marker::PhantomData, phantom_s: core::marker::PhantomData, + phantom_m: core::marker::PhantomData, } } } -impl InvoiceBuilder { +impl InvoiceBuilder { /// Helper function to set the completeness flags. - fn set_flags(self) -> InvoiceBuilder { - InvoiceBuilder:: { + fn set_flags(self) -> InvoiceBuilder { + InvoiceBuilder:: { currency: self.currency, amount: self.amount, si_prefix: self.si_prefix, @@ -519,6 +562,7 @@ impl InvoiceBui phantom_t: core::marker::PhantomData, phantom_c: core::marker::PhantomData, phantom_s: core::marker::PhantomData, + phantom_m: core::marker::PhantomData, } } @@ -540,7 +584,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 @@ -562,8 +607,9 @@ impl InvoiceBui } } -impl InvoiceBuilder { - /// Builds a `RawInvoice` if no `CreationError` occurred while construction any of the fields. +impl InvoiceBuilder { + /// Builds a [`RawInvoice`] if no [`CreationError`] occurred while construction any of the + /// fields. pub fn build_raw(self) -> Result { // If an error occurred at any time before, return it now @@ -584,20 +630,20 @@ impl InvoiceBuilder>(); let data = RawDataPart { - timestamp: timestamp, - tagged_fields: tagged_fields, + timestamp, + tagged_fields, }; Ok(RawInvoice { - hrp: hrp, - data: data, + hrp, + data, }) } } -impl InvoiceBuilder { +impl InvoiceBuilder { /// Set the description. This function is only available if no description (hash) was set. - pub fn description(mut self, description: String) -> InvoiceBuilder { + pub fn description(mut self, description: String) -> InvoiceBuilder { match Description::new(description) { Ok(d) => self.tagged_fields.push(TaggedField::Description(d)), Err(e) => self.error = Some(e), @@ -606,24 +652,36 @@ impl InvoiceBuilder InvoiceBuilder { + pub fn description_hash(mut self, description_hash: sha256::Hash) -> InvoiceBuilder { self.tagged_fields.push(TaggedField::DescriptionHash(Sha256(description_hash))); self.set_flags() } + + /// Set the description or description hash. This function is only available if no description (hash) was set. + pub fn invoice_description(self, description: InvoiceDescription) -> InvoiceBuilder { + match description { + InvoiceDescription::Direct(desc) => { + self.description(desc.clone().into_inner()) + } + InvoiceDescription::Hash(hash) => { + self.description_hash(hash.0) + } + } + } } -impl InvoiceBuilder { +impl InvoiceBuilder { /// Set the payment hash. This function is only available if no payment hash was set. - pub fn payment_hash(mut self, hash: sha256::Hash) -> InvoiceBuilder { + pub fn payment_hash(mut self, hash: sha256::Hash) -> InvoiceBuilder { self.tagged_fields.push(TaggedField::PaymentHash(Sha256(hash))); self.set_flags() } } -impl InvoiceBuilder { +impl InvoiceBuilder { /// Sets the timestamp to a specific [`SystemTime`]. #[cfg(feature = "std")] - pub fn timestamp(mut self, time: SystemTime) -> InvoiceBuilder { + pub fn timestamp(mut self, time: SystemTime) -> InvoiceBuilder { match PositiveTimestamp::from_system_time(time) { Ok(t) => self.timestamp = Some(t), Err(e) => self.error = Some(e), @@ -632,8 +690,9 @@ impl InvoiceBuilder InvoiceBuilder { + /// 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 { match PositiveTimestamp::from_duration_since_epoch(time) { Ok(t) => self.timestamp = Some(t), Err(e) => self.error = Some(e), @@ -644,34 +703,82 @@ impl InvoiceBuilder InvoiceBuilder { + pub fn current_timestamp(mut self) -> InvoiceBuilder { let now = PositiveTimestamp::from_system_time(SystemTime::now()); self.timestamp = Some(now.expect("for the foreseeable future this shouldn't happen")); self.set_flags() } } -impl InvoiceBuilder { - /// Sets `min_final_cltv_expiry`. - pub fn min_final_cltv_expiry(mut self, min_final_cltv_expiry: u64) -> InvoiceBuilder { - self.tagged_fields.push(TaggedField::MinFinalCltvExpiry(MinFinalCltvExpiry(min_final_cltv_expiry))); +impl InvoiceBuilder { + /// Sets `min_final_cltv_expiry_delta`. + pub fn min_final_cltv_expiry_delta(mut self, min_final_cltv_expiry_delta: u64) -> InvoiceBuilder { + self.tagged_fields.push(TaggedField::MinFinalCltvExpiryDelta(MinFinalCltvExpiryDelta(min_final_cltv_expiry_delta))); self.set_flags() } } -impl InvoiceBuilder { +impl InvoiceBuilder { /// Sets the payment secret and relevant features. - pub fn payment_secret(mut self, payment_secret: PaymentSecret) -> InvoiceBuilder { - let mut features = InvoiceFeatures::empty(); - features.set_variable_length_onion_required(); - features.set_payment_secret_required(); + pub fn payment_secret(mut self, payment_secret: PaymentSecret) -> InvoiceBuilder { + let mut found_features = false; + for field in self.tagged_fields.iter_mut() { + if let TaggedField::Features(f) = field { + found_features = true; + f.set_variable_length_onion_required(); + f.set_payment_secret_required(); + } + } self.tagged_fields.push(TaggedField::PaymentSecret(payment_secret)); - self.tagged_fields.push(TaggedField::Features(features)); + if !found_features { + let mut features = InvoiceFeatures::empty(); + features.set_variable_length_onion_required(); + features.set_payment_secret_required(); + self.tagged_fields.push(TaggedField::Features(features)); + } + self.set_flags() + } +} + +impl InvoiceBuilder { + /// Sets the payment metadata. + /// + /// By default features are set to *optionally* allow the sender to include the payment metadata. + /// If you wish to require that the sender include the metadata (and fail to parse the invoice if + /// they don't support payment metadata fields), you need to call + /// [`InvoiceBuilder::require_payment_metadata`] after this. + pub fn payment_metadata(mut self, payment_metadata: Vec) -> InvoiceBuilder { + self.tagged_fields.push(TaggedField::PaymentMetadata(payment_metadata)); + let mut found_features = false; + for field in self.tagged_fields.iter_mut() { + if let TaggedField::Features(f) = field { + found_features = true; + f.set_payment_metadata_optional(); + } + } + if !found_features { + let mut features = InvoiceFeatures::empty(); + features.set_payment_metadata_optional(); + self.tagged_fields.push(TaggedField::Features(features)); + } self.set_flags() } } -impl InvoiceBuilder { +impl InvoiceBuilder { + /// Sets forwarding of payment metadata as required. A reader of the invoice which does not + /// support sending payment metadata will fail to read the invoice. + pub fn require_payment_metadata(mut self) -> InvoiceBuilder { + for field in self.tagged_fields.iter_mut() { + if let TaggedField::Features(f) = field { + f.set_payment_metadata_required(); + } + } + self + } +} + +impl InvoiceBuilder { /// Sets the `basic_mpp` feature as optional. pub fn basic_mpp(mut self) -> Self { for field in self.tagged_fields.iter_mut() { @@ -683,7 +790,7 @@ impl InvoiceBuilder { +impl InvoiceBuilder { /// Builds and signs an invoice using the supplied `sign_function`. This function MAY NOT fail /// and MUST produce a recoverable signature valid for the given hash and if applicable also for /// the included payee public key. @@ -739,17 +846,17 @@ impl SignedRawInvoice { (self.raw_invoice, self.hash, self.signature) } - /// The `RawInvoice` which was signed. + /// The [`RawInvoice`] which was signed. pub fn raw_invoice(&self) -> &RawInvoice { &self.raw_invoice } - /// The hash of the `RawInvoice` that was signed. + /// The hash of the [`RawInvoice`] that was signed. pub fn signable_hash(&self) -> &[u8; 32] { &self.hash } - /// InvoiceSignature for the invoice. + /// Signature for the invoice. pub fn signature(&self) -> &InvoiceSignature { &self.signature } @@ -779,7 +886,7 @@ impl SignedRawInvoice { recovered_pub_key = Some(recovered); } - let pub_key = included_pub_key.or_else(|| recovered_pub_key.as_ref()) + let pub_key = included_pub_key.or(recovered_pub_key.as_ref()) .expect("One is always present"); let hash = Message::from_slice(&self.hash[..]) @@ -804,6 +911,7 @@ impl SignedRawInvoice { /// /// The following example would extract the first B. /// +/// ```ignore /// enum Enum { /// A(u8), /// B(u16) @@ -812,6 +920,7 @@ impl SignedRawInvoice { /// let elements = vec![Enum::A(1), Enum::A(2), Enum::B(3), Enum::A(4)]; /// /// assert_eq!(find_extract!(elements.iter(), Enum::B(x), x), Some(3u16)); +/// ``` macro_rules! find_extract { ($iter:expr, $enm:pat, $enm_var:ident) => { find_all_extract!($iter, $enm, $enm_var).next() @@ -823,6 +932,7 @@ macro_rules! find_extract { /// /// The following example would extract all A. /// +/// ```ignore /// enum Enum { /// A(u8), /// B(u16) @@ -834,6 +944,7 @@ macro_rules! find_extract { /// find_all_extract!(elements.iter(), Enum::A(x), x).collect::>(), /// vec![1u8, 2u8, 4u8] /// ); +/// ``` macro_rules! find_all_extract { ($iter:expr, $enm:pat, $enm_var:ident) => { $iter.filter_map(|tf| match *tf { @@ -863,11 +974,11 @@ impl RawInvoice { ) } - /// Signs the invoice using the supplied `sign_function`. This function MAY fail with an error - /// of type `E`. Since the signature of a `SignedRawInvoice` is not required to be valid there + /// Signs the invoice using the supplied `sign_method`. This function MAY fail with an error of + /// type `E`. Since the signature of a [`SignedRawInvoice`] is not required to be valid there /// are no constraints regarding the validity of the produced signature. /// - /// (C-not exported) As we don't currently support passing function pointers into methods + /// This is not exported to bindings users as we don't currently support passing function pointers into methods /// explicitly. pub fn sign(self, sign_method: F) -> Result where F: FnOnce(&Message) -> Result @@ -886,7 +997,7 @@ impl RawInvoice { /// Returns an iterator over all tagged fields with known semantics. /// - /// (C-not exported) As there is not yet a manual mapping for a FilterMap + /// This is not exported to bindings users as there is not yet a manual mapping for a FilterMap pub fn known_tagged_fields(&self) -> FilterMap, fn(&RawTaggedField) -> Option<&TaggedField>> { @@ -923,19 +1034,23 @@ impl RawInvoice { find_extract!(self.known_tagged_fields(), TaggedField::ExpiryTime(ref x), x) } - pub fn min_final_cltv_expiry(&self) -> Option<&MinFinalCltvExpiry> { - find_extract!(self.known_tagged_fields(), TaggedField::MinFinalCltvExpiry(ref x), x) + pub fn min_final_cltv_expiry_delta(&self) -> Option<&MinFinalCltvExpiryDelta> { + find_extract!(self.known_tagged_fields(), TaggedField::MinFinalCltvExpiryDelta(ref x), x) } pub fn payment_secret(&self) -> Option<&PaymentSecret> { find_extract!(self.known_tagged_fields(), TaggedField::PaymentSecret(ref x), x) } + pub fn payment_metadata(&self) -> Option<&Vec> { + find_extract!(self.known_tagged_fields(), TaggedField::PaymentMetadata(ref x), x) + } + pub fn features(&self) -> Option<&InvoiceFeatures> { find_extract!(self.known_tagged_fields(), TaggedField::Features(ref x), x) } - /// (C-not exported) as we don't support Vec<&NonOpaqueType> + /// This is not exported to bindings users as we don't support Vec<&NonOpaqueType> pub fn fallbacks(&self) -> Vec<&Fallback> { find_all_extract!(self.known_tagged_fields(), TaggedField::Fallback(ref x), x).collect() } @@ -960,12 +1075,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 +1098,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 @@ -1004,13 +1123,18 @@ impl PositiveTimestamp { } #[cfg(feature = "std")] -impl Into for PositiveTimestamp { - fn into(self) -> SystemTime { - SystemTime::UNIX_EPOCH + self.0 +impl From for SystemTime { + fn from(val: PositiveTimestamp) -> Self { + SystemTime::UNIX_EPOCH + val.0 } } impl Invoice { + /// The hash of the [`RawInvoice`] that was signed. + pub fn signable_hash(&self) -> [u8; 32] { + self.signed_invoice.hash + } + /// Transform the `Invoice` into it's unchecked version pub fn into_signed_raw(self) -> SignedRawInvoice { self.signed_invoice @@ -1115,7 +1239,7 @@ impl Invoice { Ok(()) } - /// Constructs an `Invoice` from a `SignedRawInvoice` by checking all its invariants. + /// Constructs an `Invoice` from a [`SignedRawInvoice`] by checking all its invariants. /// ``` /// use lightning_invoice::*; /// @@ -1137,7 +1261,7 @@ impl Invoice { /// ``` pub fn from_signed(signed_invoice: SignedRawInvoice) -> Result { let invoice = Invoice { - signed_invoice: signed_invoice, + signed_invoice, }; invoice.check_field_counts()?; invoice.check_feature_bits()?; @@ -1160,7 +1284,7 @@ impl Invoice { /// Returns an iterator over all tagged fields of this Invoice. /// - /// (C-not exported) As there is not yet a manual mapping for a FilterMap + /// This is not exported to bindings users as there is not yet a manual mapping for a FilterMap pub fn tagged_fields(&self) -> FilterMap, fn(&RawTaggedField) -> Option<&TaggedField>> { self.signed_invoice.raw_invoice().known_tagged_fields() @@ -1173,11 +1297,11 @@ impl Invoice { /// Return the description or a hash of it for longer ones /// - /// (C-not exported) because we don't yet export InvoiceDescription + /// This is not exported to bindings users because we don't yet export InvoiceDescription pub fn description(&self) -> InvoiceDescription { - if let Some(ref direct) = self.signed_invoice.description() { + if let Some(direct) = self.signed_invoice.description() { return InvoiceDescription::Direct(direct); - } else if let Some(ref hash) = self.signed_invoice.description_hash() { + } else if let Some(hash) = self.signed_invoice.description_hash() { return InvoiceDescription::Hash(hash); } unreachable!("ensured by constructor"); @@ -1193,6 +1317,11 @@ impl Invoice { self.signed_invoice.payment_secret().expect("was checked by constructor") } + /// Get the payment metadata blob if one was included in the invoice + pub fn payment_metadata(&self) -> Option<&Vec> { + self.signed_invoice.payment_metadata() + } + /// Get the invoice features if they were included in the invoice pub fn features(&self) -> Option<&InvoiceFeatures> { self.signed_invoice.features() @@ -1203,6 +1332,12 @@ impl Invoice { self.signed_invoice.recover_payee_pub_key().expect("was checked by constructor").0 } + /// Returns the Duration since the Unix epoch at which the invoice expires. + /// Returning None if overflow occurred. + pub fn expires_at(&self) -> Option { + self.duration_since_epoch().checked_add(self.expiry_time()) + } + /// Returns the invoice's expiry time, if present, otherwise [`DEFAULT_EXPIRY_TIME`]. pub fn expiry_time(&self) -> Duration { self.signed_invoice.expiry_time() @@ -1225,6 +1360,20 @@ impl Invoice { } } + /// Returns the Duration remaining until the invoice expires. + #[cfg(feature = "std")] + pub fn duration_until_expiry(&self) -> Duration { + SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) + .map(|now| self.expiration_remaining_from_epoch(now)) + .unwrap_or(Duration::from_nanos(0)) + } + + /// Returns the Duration remaining until the invoice expires given the current time. + /// `time` is the timestamp as a duration since the Unix epoch. + pub fn expiration_remaining_from_epoch(&self, time: Duration) -> Duration { + self.expires_at().map(|x| x.checked_sub(time)).flatten().unwrap_or(Duration::from_nanos(0)) + } + /// Returns whether the expiry time would pass at the given point in time. /// `at_time` is the timestamp as a duration since the Unix epoch. pub fn would_expire(&self, at_time: Duration) -> bool { @@ -1233,21 +1382,40 @@ impl Invoice { .unwrap_or_else(|| Duration::new(u64::max_value(), 1_000_000_000 - 1)) < at_time } - /// Returns the invoice's `min_final_cltv_expiry` time, if present, otherwise - /// [`DEFAULT_MIN_FINAL_CLTV_EXPIRY`]. - pub fn min_final_cltv_expiry(&self) -> u64 { - self.signed_invoice.min_final_cltv_expiry() + /// Returns the invoice's `min_final_cltv_expiry_delta` time, if present, otherwise + /// [`DEFAULT_MIN_FINAL_CLTV_EXPIRY_DELTA`]. + pub fn min_final_cltv_expiry_delta(&self) -> u64 { + self.signed_invoice.min_final_cltv_expiry_delta() .map(|x| x.0) - .unwrap_or(DEFAULT_MIN_FINAL_CLTV_EXPIRY) + .unwrap_or(DEFAULT_MIN_FINAL_CLTV_EXPIRY_DELTA) } /// Returns a list of all fallback addresses /// - /// (C-not exported) as we don't support Vec<&NonOpaqueType> + /// This is not exported to bindings users as we don't support Vec<&NonOpaqueType> pub fn fallbacks(&self) -> Vec<&Fallback> { self.signed_invoice.fallbacks() } + /// Returns a list of all fallback addresses as [`Address`]es + pub fn fallback_addresses(&self) -> Vec
{ + self.fallbacks().iter().map(|fallback| { + let payload = match fallback { + Fallback::SegWitProgram { version, program } => { + Payload::WitnessProgram { version: *version, program: program.to_vec() } + } + Fallback::PubKeyHash(pkh) => { + Payload::PubkeyHash(*pkh) + } + Fallback::ScriptHash(sh) => { + Payload::ScriptHash(*sh) + } + }; + + Address { payload, network: self.network() } + }).collect() + } + /// Returns a list of all routes included in the invoice pub fn private_routes(&self) -> Vec<&PrivateRoute> { self.signed_invoice.private_routes() @@ -1265,12 +1433,19 @@ impl Invoice { self.signed_invoice.currency() } + /// Returns the network for which the invoice was issued + /// + /// This is not exported to bindings users, see [`Self::currency`] instead. + pub fn network(&self) -> Network { + self.signed_invoice.currency().into() + } + /// Returns the amount if specified in the invoice as millisatoshis. pub fn amount_milli_satoshis(&self) -> Option { self.signed_invoice.amount_pico_btc().map(|v| v / 10) } - /// Returns the amount if specified in the invoice as pico . + /// Returns the amount if specified in the invoice as pico BTC. fn amount_pico_btc(&self) -> Option { self.signed_invoice.amount_pico_btc() } @@ -1291,10 +1466,11 @@ impl TaggedField { TaggedField::PayeePubKey(_) => constants::TAG_PAYEE_PUB_KEY, TaggedField::DescriptionHash(_) => constants::TAG_DESCRIPTION_HASH, TaggedField::ExpiryTime(_) => constants::TAG_EXPIRY_TIME, - TaggedField::MinFinalCltvExpiry(_) => constants::TAG_MIN_FINAL_CLTV_EXPIRY, + TaggedField::MinFinalCltvExpiryDelta(_) => constants::TAG_MIN_FINAL_CLTV_EXPIRY_DELTA, TaggedField::Fallback(_) => constants::TAG_FALLBACK, TaggedField::PrivateRoute(_) => constants::TAG_PRIVATE_ROUTE, TaggedField::PaymentSecret(_) => constants::TAG_PAYMENT_SECRET, + TaggedField::PaymentMetadata(_) => constants::TAG_PAYMENT_METADATA, TaggedField::Features(_) => constants::TAG_FEATURES, }; @@ -1305,7 +1481,7 @@ impl TaggedField { impl Description { /// Creates a new `Description` if `description` is at most 1023 __bytes__ long, - /// returns `CreationError::DescriptionTooLong` otherwise + /// returns [`CreationError::DescriptionTooLong`] otherwise /// /// Please note that single characters may use more than one byte due to UTF8 encoding. pub fn new(description: String) -> Result { @@ -1316,15 +1492,15 @@ impl Description { } } - /// Returns the underlying description `String` + /// Returns the underlying description [`String`] pub fn into_inner(self) -> String { self.0 } } -impl Into for Description { - fn into(self) -> String { - self.into_inner() +impl From for String { + fn from(val: Description) -> Self { + val.into_inner() } } @@ -1356,9 +1532,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 @@ -1366,7 +1542,7 @@ impl ExpiryTime { self.0.as_secs() } - /// Returns a reference to the underlying `Duration` (=expiry time) + /// Returns a reference to the underlying [`Duration`] (=expiry time) pub fn as_duration(&self) -> &Duration { &self.0 } @@ -1388,9 +1564,9 @@ impl PrivateRoute { } } -impl Into for PrivateRoute { - fn into(self) -> RouteHint { - self.into_inner() +impl From for RouteHint { + fn from(val: PrivateRoute) -> Self { + val.into_inner() } } @@ -1418,10 +1594,10 @@ impl Deref for SignedRawInvoice { } } -/// Errors that may occur when constructing a new `RawInvoice` or `Invoice` +/// Errors that may occur when constructing a new [`RawInvoice`] or [`Invoice`] #[derive(Eq, PartialEq, Debug, Clone)] pub enum CreationError { - /// The supplied description string was longer than 639 __bytes__ (see [`Description::new(…)`](./struct.Description.html#method.new)) + /// The supplied description string was longer than 639 __bytes__ (see [`Description::new`]) DescriptionTooLong, /// The specified route has too many hops and can't be encoded @@ -1438,6 +1614,11 @@ pub enum CreationError { /// /// [phantom invoices]: crate::utils::create_phantom_invoice MissingRouteHints, + + /// The provided `min_final_cltv_expiry_delta` was less than [`MIN_FINAL_CLTV_EXPIRY_DELTA`]. + /// + /// [`MIN_FINAL_CLTV_EXPIRY_DELTA`]: lightning::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA + MinFinalCltvExpiryDeltaTooShort, } impl Display for CreationError { @@ -1448,6 +1629,8 @@ impl Display for CreationError { CreationError::TimestampOutOfBounds => f.write_str("The Unix timestamp of the supplied date is less than zero or greater than 35-bits"), CreationError::InvalidAmount => f.write_str("The supplied millisatoshi amount was greater than the total bitcoin supply"), CreationError::MissingRouteHints => f.write_str("The invoice required route hints and they weren't provided"), + CreationError::MinFinalCltvExpiryDeltaTooShort => f.write_str( + "The supplied final CLTV expiry delta was less than LDK's `MIN_FINAL_CLTV_EXPIRY_DELTA`"), } } } @@ -1455,7 +1638,7 @@ impl Display for CreationError { #[cfg(feature = "std")] impl std::error::Error for CreationError { } -/// Errors that may occur when converting a `RawInvoice` to an `Invoice`. They relate to the +/// Errors that may occur when converting a [`RawInvoice`] to an [`Invoice`]. They relate to the /// requirements sections in BOLT #11 #[derive(Eq, PartialEq, Debug, Clone)] pub enum SemanticError { @@ -1511,7 +1694,7 @@ impl Display for SemanticError { #[cfg(feature = "std")] impl std::error::Error for SemanticError { } -/// When signing using a fallible method either an user-supplied `SignError` or a `CreationError` +/// When signing using a fallible method either an user-supplied `SignError` or a [`CreationError`] /// may occur. #[derive(Eq, PartialEq, Debug, Clone)] pub enum SignOrCreationError { @@ -1542,7 +1725,7 @@ impl<'de> Deserialize<'de> for Invoice { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de> { let bolt11 = String::deserialize(deserializer)? .parse::() - .map_err(|e| D::Error::custom(format!("{:?}", e)))?; + .map_err(|e| D::Error::custom(format_args!("{:?}", e)))?; Ok(bolt11) } @@ -1550,21 +1733,22 @@ impl<'de> Deserialize<'de> for Invoice { #[cfg(test)] mod test { + use bitcoin::Script; use bitcoin_hashes::hex::FromHex; use bitcoin_hashes::sha256; #[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 +1759,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 +1780,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 +1801,7 @@ mod test { "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap())).into(), Description( - ::Description::new( + crate::Description::new( "Please consider supporting this project".to_owned() ).unwrap() ).into(), @@ -1653,7 +1837,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 +1849,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 +1871,7 @@ mod test { "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap())).into(), Description( - ::Description::new( + crate::Description::new( "Please consider supporting this project".to_owned() ).unwrap() ).into(), @@ -1712,11 +1896,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,14 +1926,14 @@ 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)); // Multiple payment secrets let invoice = { - let mut invoice = invoice_template.clone(); + let mut invoice = invoice_template; invoice.data.tagged_fields.push(PaymentSecret(payment_secret).into()); invoice.data.tagged_fields.push(PaymentSecret(payment_secret).into()); invoice.sign::<_, ()>(|hash| Ok(Secp256k1::new().sign_ecdsa_recoverable(hash, &private_key))) @@ -1756,7 +1943,7 @@ mod test { #[test] fn test_builder_amount() { - use ::*; + use crate::*; let builder = InvoiceBuilder::new(Currency::Bitcoin) .description("Test".into()) @@ -1772,7 +1959,7 @@ mod test { assert_eq!(invoice.hrp.raw_amount, Some(15)); - let invoice = builder.clone() + let invoice = builder .amount_milli_satoshis(150) .build_raw() .unwrap(); @@ -1783,7 +1970,7 @@ mod test { #[test] fn test_builder_fail() { - use ::*; + use crate::*; use lightning::routing::router::RouteHintHop; use std::iter::FromIterator; use secp256k1::PublicKey; @@ -1791,7 +1978,7 @@ mod test { let builder = InvoiceBuilder::new(Currency::Bitcoin) .payment_hash(sha256::Hash::from_slice(&[0;32][..]).unwrap()) .duration_since_epoch(Duration::from_secs(1234567)) - .min_final_cltv_expiry(144); + .min_final_cltv_expiry_delta(144); let too_long_string = String::from_iter( (0..1024).map(|_| '?') @@ -1826,7 +2013,7 @@ mod test { .build_raw(); assert_eq!(long_route_res, Err(CreationError::RouteTooLong)); - let sign_error_res = builder.clone() + let sign_error_res = builder .description("Test".into()) .payment_secret(PaymentSecret([0; 32])) .try_build_signed(|_| { @@ -1837,7 +2024,7 @@ mod test { #[test] fn test_builder_ok() { - use ::*; + use crate::*; use lightning::routing::router::RouteHintHop; use secp256k1::Secp256k1; use secp256k1::{SecretKey, PublicKey}; @@ -1856,7 +2043,7 @@ mod test { let route_1 = RouteHint(vec![ RouteHintHop { - src_node_id: public_key.clone(), + src_node_id: public_key, short_channel_id: de::parse_int_be(&[123; 8], 256).expect("short chan ID slice too big?"), fees: RoutingFees { base_msat: 2, @@ -1867,7 +2054,7 @@ mod test { htlc_maximum_msat: None, }, RouteHintHop { - src_node_id: public_key.clone(), + src_node_id: public_key, short_channel_id: de::parse_int_be(&[42; 8], 256).expect("short chan ID slice too big?"), fees: RoutingFees { base_msat: 3, @@ -1881,7 +2068,7 @@ mod test { let route_2 = RouteHint(vec![ RouteHintHop { - src_node_id: public_key.clone(), + src_node_id: public_key, short_channel_id: 0, fees: RoutingFees { base_msat: 4, @@ -1892,7 +2079,7 @@ mod test { htlc_maximum_msat: None, }, RouteHintHop { - src_node_id: public_key.clone(), + src_node_id: public_key, short_channel_id: de::parse_int_be(&[1; 8], 256).expect("short chan ID slice too big?"), fees: RoutingFees { base_msat: 5, @@ -1907,10 +2094,10 @@ mod test { let builder = InvoiceBuilder::new(Currency::BitcoinTestnet) .amount_milli_satoshis(123) .duration_since_epoch(Duration::from_secs(1234567)) - .payee_pub_key(public_key.clone()) + .payee_pub_key(public_key) .expiry_time(Duration::from_secs(54321)) - .min_final_cltv_expiry(144) - .fallback(Fallback::PubKeyHash([0;20])) + .min_final_cltv_expiry_delta(144) + .fallback(Fallback::PubKeyHash(PubkeyHash::from_slice(&[0;20]).unwrap())) .private_route(route_1.clone()) .private_route(route_2.clone()) .description_hash(sha256::Hash::from_slice(&[3;32][..]).unwrap()) @@ -1935,8 +2122,10 @@ mod test { ); assert_eq!(invoice.payee_pub_key(), Some(&public_key)); assert_eq!(invoice.expiry_time(), Duration::from_secs(54321)); - assert_eq!(invoice.min_final_cltv_expiry(), 144); - assert_eq!(invoice.fallbacks(), vec![&Fallback::PubKeyHash([0;20])]); + assert_eq!(invoice.min_final_cltv_expiry_delta(), 144); + assert_eq!(invoice.fallbacks(), vec![&Fallback::PubKeyHash(PubkeyHash::from_slice(&[0;20]).unwrap())]); + let address = Address::from_script(&Script::new_p2pkh(&PubkeyHash::from_slice(&[0;20]).unwrap()), Network::Testnet).unwrap(); + assert_eq!(invoice.fallback_addresses(), vec![address]); assert_eq!(invoice.private_routes(), vec![&PrivateRoute(route_1), &PrivateRoute(route_2)]); assert_eq!( invoice.description(), @@ -1944,7 +2133,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 +2146,7 @@ mod test { #[test] fn test_default_values() { - use ::*; + use crate::*; use secp256k1::Secp256k1; use secp256k1::SecretKey; @@ -1971,14 +2165,14 @@ mod test { .unwrap(); let invoice = Invoice::from_signed(signed_invoice).unwrap(); - assert_eq!(invoice.min_final_cltv_expiry(), DEFAULT_MIN_FINAL_CLTV_EXPIRY); + assert_eq!(invoice.min_final_cltv_expiry_delta(), DEFAULT_MIN_FINAL_CLTV_EXPIRY_DELTA); assert_eq!(invoice.expiry_time(), Duration::from_secs(DEFAULT_EXPIRY_TIME)); assert!(!invoice.would_expire(Duration::from_secs(1234568))); } #[test] fn test_expiration() { - use ::*; + use crate::*; use secp256k1::Secp256k1; use secp256k1::SecretKey;