X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning-invoice%2Fsrc%2Flib.rs;h=2ce58f296f9b26b06ec4c92d8b634a57f680d5b3;hb=2024c5e1049ef399fb24e5c466924f980e949c69;hp=5a9c094c63be6d1190249df462a4bd4c3a794154;hpb=236342332ea272d47be1043197a3817d5d0482da;p=rust-lightning diff --git a/lightning-invoice/src/lib.rs b/lightning-invoice/src/lib.rs index 5a9c094c..2ce58f29 100644 --- a/lightning-invoice/src/lib.rs +++ b/lightning-invoice/src/lib.rs @@ -3,6 +3,7 @@ #![deny(non_camel_case_types)] #![deny(non_snake_case)] #![deny(unused_mut)] +#![deny(broken_intra_doc_links)] #![cfg_attr(feature = "strict", deny(warnings))] @@ -14,6 +15,7 @@ //! * 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 +pub mod utils; extern crate bech32; extern crate bitcoin_hashes; @@ -24,20 +26,21 @@ extern crate secp256k1; use bech32::u5; use bitcoin_hashes::Hash; use bitcoin_hashes::sha256; +use lightning::ln::PaymentSecret; use lightning::ln::features::InvoiceFeatures; #[cfg(any(doc, test))] use lightning::routing::network_graph::RoutingFees; -use lightning::routing::router::RouteHintHop; +use lightning::routing::router::RouteHint; use secp256k1::key::PublicKey; use secp256k1::{Message, Secp256k1}; use secp256k1::recovery::RecoverableSignature; -use std::ops::Deref; +use std::fmt::{Display, Formatter, self}; use std::iter::FilterMap; +use std::ops::Deref; use std::slice::Iter; use std::time::{SystemTime, Duration, UNIX_EPOCH}; -use std::fmt::{Display, Formatter, self}; mod de; mod ser; @@ -54,6 +57,20 @@ const SYSTEM_TIME_MAX_UNIX_TIMESTAMP: u64 = std::i32::MAX as u64; /// it should be rather low as long as we still have to support 32bit time representations const MAX_EXPIRY_TIME: u64 = 60 * 60 * 24 * 356; +/// Default expiry time as defined by [BOLT 11]. +/// +/// [BOLT 11]: https://github.com/lightningnetwork/lightning-rfc/blob/master/11-payment-encoding.md +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`]. +/// +/// [BOLT 11]: https://github.com/lightningnetwork/lightning-rfc/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; + /// This function is used as a static assert for the size of `SystemTime`. If the crate fails to /// compile due to it this indicates that your system uses unexpected bounds for `SystemTime`. You /// can remove this functions and run the test `test_system_time_bounds_assumptions`. In any case, @@ -136,6 +153,7 @@ pub fn check_platform() { /// .description("Coins pls!".into()) /// .payment_hash(payment_hash) /// .current_timestamp() +/// .min_final_cltv_expiry(144) /// .build_signed(|hash| { /// Secp256k1::new().sign_recoverable(hash, &private_key) /// }) @@ -151,8 +169,10 @@ pub fn check_platform() { /// * `D`: exactly one `Description` or `DescriptionHash` /// * `H`: exactly one `PaymentHash` /// * `T`: the timestamp is set +/// +/// (C-not exported) 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, @@ -163,6 +183,8 @@ pub struct InvoiceBuilder { phantom_d: std::marker::PhantomData, phantom_h: std::marker::PhantomData, phantom_t: std::marker::PhantomData, + phantom_c: std::marker::PhantomData, + phantom_s: std::marker::PhantomData, } /// Represents a syntactically and semantically correct lightning BOLT11 invoice. @@ -178,6 +200,9 @@ 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 +/// practically impossible to use safely in languages like C. #[derive(Eq, PartialEq, Debug, Clone)] pub enum InvoiceDescription<'f> { /// Reference to the directly supplied description in the invoice @@ -207,7 +232,7 @@ pub struct SignedRawInvoice { hash: [u8; 32], /// signature of the payment request - signature: Signature, + signature: InvoiceSignature, } /// Represents an syntactically correct Invoice for a payment on the lightning network, @@ -225,6 +250,8 @@ 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)] pub struct RawHrp { /// The currency deferred from the 3rd and 4th character of the bech32 transaction @@ -283,6 +310,9 @@ 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 + /// isn't the most critical to expose. pub fn values_desc() -> &'static [SiPrefix] { use SiPrefix::*; static VALUES: [SiPrefix; 4] = [Milli, Micro, Nano, Pico]; @@ -302,11 +332,16 @@ pub enum Currency { /// Bitcoin regtest Regtest, - /// Bitcoin simnet/signet + /// Bitcoin simnet Simnet, + + /// Bitcoin signet + Signet, } /// Tagged field which may have an unknown tag +/// +/// (C-not exported) as we don't currently support TaggedField #[derive(Eq, PartialEq, Debug, Clone)] pub enum RawTaggedField { /// Parsed tagged field with known tag @@ -318,6 +353,9 @@ pub enum RawTaggedField { /// Tagged field with known tag /// /// 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 +/// in the variant. #[allow(missing_docs)] #[derive(Eq, PartialEq, Debug, Clone)] pub enum TaggedField { @@ -328,7 +366,7 @@ pub enum TaggedField { ExpiryTime(ExpiryTime), MinFinalCltvExpiry(MinFinalCltvExpiry), Fallback(Fallback), - Route(RouteHint), + PrivateRoute(PrivateRoute), PaymentSecret(PaymentSecret), Features(InvoiceFeatures), } @@ -348,10 +386,6 @@ pub struct Description(String); #[derive(Eq, PartialEq, Debug, Clone)] pub struct PayeePubKey(pub PublicKey); -/// 256-bit payment secret -#[derive(Eq, PartialEq, Debug, Clone)] -pub struct PaymentSecret(pub [u8; 32]); - /// Positive duration that defines when (relatively to the timestamp) in the future the invoice /// expires /// @@ -381,7 +415,7 @@ pub enum Fallback { /// Recoverable signature #[derive(Eq, PartialEq, Debug, Clone)] -pub struct Signature(pub RecoverableSignature); +pub struct InvoiceSignature(pub RecoverableSignature); /// Private routing information /// @@ -389,7 +423,7 @@ pub struct Signature(pub RecoverableSignature); /// The encoded route has to be <1024 5bit characters long (<=639 bytes or <=12 hops) /// #[derive(Eq, PartialEq, Debug, Clone)] -pub struct RouteHint(Vec); +pub struct PrivateRoute(RouteHint); /// Tag constants as specified in BOLT11 #[allow(missing_docs)] @@ -401,12 +435,12 @@ pub mod constants { pub const TAG_EXPIRY_TIME: u8 = 6; pub const TAG_MIN_FINAL_CLTV_EXPIRY: u8 = 24; pub const TAG_FALLBACK: u8 = 9; - pub const TAG_ROUTE: u8 = 3; + pub const TAG_PRIVATE_ROUTE: u8 = 3; pub const TAG_PAYMENT_SECRET: u8 = 16; 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 { @@ -421,14 +455,16 @@ impl InvoiceBuilder { phantom_d: std::marker::PhantomData, phantom_h: std::marker::PhantomData, phantom_t: std::marker::PhantomData, + phantom_c: std::marker::PhantomData, + phantom_s: std::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, @@ -439,6 +475,8 @@ impl InvoiceBuilder { phantom_d: std::marker::PhantomData, phantom_h: std::marker::PhantomData, phantom_t: std::marker::PhantomData, + phantom_c: std::marker::PhantomData, + phantom_s: std::marker::PhantomData, } } @@ -459,12 +497,6 @@ impl InvoiceBuilder { self } - /// Sets the payment secret - pub fn payment_secret(mut self, payment_secret: PaymentSecret) -> Self { - self.tagged_fields.push(TaggedField::PaymentSecret(payment_secret)); - self - } - /// Sets the expiry time pub fn expiry_time(mut self, expiry_time: Duration) -> Self { match ExpiryTime::from_duration(expiry_time) { @@ -474,12 +506,6 @@ impl InvoiceBuilder { self } - /// Sets `min_final_cltv_expiry`. - pub fn min_final_cltv_expiry(mut self, min_final_cltv_expiry: u64) -> Self { - self.tagged_fields.push(TaggedField::MinFinalCltvExpiry(MinFinalCltvExpiry(min_final_cltv_expiry))); - self - } - /// Adds a fallback address. pub fn fallback(mut self, fallback: Fallback) -> Self { self.tagged_fields.push(TaggedField::Fallback(fallback)); @@ -487,23 +513,16 @@ impl InvoiceBuilder { } /// Adds a private route. - pub fn route(mut self, route: Vec) -> Self { - match RouteHint::new(route) { - Ok(r) => self.tagged_fields.push(TaggedField::Route(r)), + pub fn private_route(mut self, hint: RouteHint) -> Self { + match PrivateRoute::new(hint) { + Ok(r) => self.tagged_fields.push(TaggedField::PrivateRoute(r)), Err(e) => self.error = Some(e), } self } - - /// Adds a features field which indicates the set of supported protocol extensions which the - /// origin node supports. - pub fn features(mut self, features: InvoiceFeatures) -> Self { - self.tagged_fields.push(TaggedField::Features(features)); - self - } } -impl InvoiceBuilder { +impl InvoiceBuilder { /// Builds a `RawInvoice` if no `CreationError` occurred while construction any of the fields. pub fn build_raw(self) -> Result { @@ -536,9 +555,9 @@ impl InvoiceBuilder { } } -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), @@ -547,23 +566,23 @@ impl InvoiceBuilder { } /// Set the description hash. This function is only available if no description (hash) was set. - pub fn description_hash(mut self, description_hash: sha256::Hash) -> InvoiceBuilder { + pub fn description_hash(mut self, description_hash: sha256::Hash) -> InvoiceBuilder { self.tagged_fields.push(TaggedField::DescriptionHash(Sha256(description_hash))); self.set_flags() } } -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. - 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), @@ -573,14 +592,48 @@ impl InvoiceBuilder { } /// Sets the timestamp to the current UNIX timestamp. - pub fn current_timestamp(mut self) -> 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 { +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))); + self.set_flags() + } +} + +impl InvoiceBuilder { + /// Sets the payment secret and relevant features. + pub fn payment_secret(mut self, payment_secret: PaymentSecret) -> InvoiceBuilder { + let features = InvoiceFeatures::empty() + .set_variable_length_onion_required() + .set_payment_secret_required(); + self.tagged_fields.push(TaggedField::PaymentSecret(payment_secret)); + self.tagged_fields.push(TaggedField::Features(features)); + self.set_flags() + } +} + +impl InvoiceBuilder { + /// Sets the `basic_mpp` feature as optional. + pub fn basic_mpp(mut self) -> Self { + self.tagged_fields = self.tagged_fields + .drain(..) + .map(|field| match field { + TaggedField::Features(f) => TaggedField::Features(f.set_basic_mpp_optional()), + _ => field, + }) + .collect(); + self + } +} + +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. @@ -619,6 +672,7 @@ impl InvoiceBuilder { }; invoice.check_field_counts().expect("should be ensured by type signature of builder"); + invoice.check_feature_bits().expect("should be ensured by type signature of builder"); Ok(invoice) } @@ -630,7 +684,7 @@ impl SignedRawInvoice { /// 1. raw invoice /// 2. hash of the raw invoice /// 3. signature - pub fn into_parts(self) -> (RawInvoice, [u8; 32], Signature) { + pub fn into_parts(self) -> (RawInvoice, [u8; 32], InvoiceSignature) { (self.raw_invoice, self.hash, self.signature) } @@ -644,8 +698,8 @@ impl SignedRawInvoice { &self.hash } - /// Signature for the invoice. - pub fn signature(&self) -> &Signature { + /// InvoiceSignature for the invoice. + pub fn signature(&self) -> &InvoiceSignature { &self.signature } @@ -697,7 +751,7 @@ impl SignedRawInvoice { /// Finds the first element of an enum stream of a given variant and extracts one member of the /// variant. If no element was found `None` gets returned. /// -/// The following example would extract the first +/// The following example would extract the first B. /// ``` /// use Enum::* /// @@ -711,18 +765,42 @@ impl SignedRawInvoice { /// assert_eq!(find_extract!(elements.iter(), Enum::B(ref x), x), Some(3u16)) /// ``` macro_rules! find_extract { - ($iter:expr, $enm:pat, $enm_var:ident) => { + ($iter:expr, $enm:pat, $enm_var:ident) => { + find_all_extract!($iter, $enm, $enm_var).next() + }; +} + +/// Finds the all elements of an enum stream of a given variant and extracts one member of the +/// variant through an iterator. +/// +/// The following example would extract all A. +/// ``` +/// use Enum::* +/// +/// enum Enum { +/// A(u8), +/// B(u16) +/// } +/// +/// let elements = vec![A(1), A(2), B(3), A(4)] +/// +/// assert_eq!( +/// find_all_extract!(elements.iter(), Enum::A(ref 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 { $enm => Some($enm_var), _ => None, - }).next() + }) }; } #[allow(missing_docs)] impl RawInvoice { - /// Hash the HRP as bytes and signatureless data part. - fn hash_from_parts(hrp_bytes: &[u8], data_without_signature: &[u5]) -> [u8; 32] { + /// Construct the invoice's HRP and signatureless data into a preimage to be hashed. + pub(crate) fn construct_invoice_preimage(hrp_bytes: &[u8], data_without_signature: &[u5]) -> Vec { use bech32::FromBase32; let mut preimage = Vec::::from(hrp_bytes); @@ -741,7 +819,12 @@ impl RawInvoice { preimage.extend_from_slice(&Vec::::from_base32(&data_part) .expect("No padding error may occur due to appended zero above.")); + preimage + } + /// Hash the HRP as bytes and signatureless data part. + fn hash_from_parts(hrp_bytes: &[u8], data_without_signature: &[u5]) -> [u8; 32] { + let preimage = RawInvoice::construct_invoice_preimage(hrp_bytes, data_without_signature); let mut hash: [u8; 32] = Default::default(); hash.copy_from_slice(&sha256::Hash::hash(&preimage)[..]); hash @@ -760,6 +843,9 @@ 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 /// are no constraints regarding the validity of the produced signature. + /// + /// (C-not exported) 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 { @@ -771,11 +857,13 @@ impl RawInvoice { Ok(SignedRawInvoice { raw_invoice: self, hash: raw_hash, - signature: Signature(signature), + signature: InvoiceSignature(signature), }) } /// Returns an iterator over all tagged fields with known semantics. + /// + /// (C-not exported) As there is not yet a manual mapping for a FilterMap pub fn known_tagged_fields(&self) -> FilterMap, fn(&RawTaggedField) -> Option<&TaggedField>> { @@ -824,18 +912,13 @@ impl RawInvoice { find_extract!(self.known_tagged_fields(), TaggedField::Features(ref x), x) } + /// (C-not exported) as we don't support Vec<&NonOpaqueType> pub fn fallbacks(&self) -> Vec<&Fallback> { - self.known_tagged_fields().filter_map(|tf| match tf { - &TaggedField::Fallback(ref f) => Some(f), - _ => None, - }).collect::>() + find_all_extract!(self.known_tagged_fields(), TaggedField::Fallback(ref x), x).collect() } - pub fn routes(&self) -> Vec<&RouteHint> { - self.known_tagged_fields().filter_map(|tf| match tf { - &TaggedField::Route(ref r) => Some(r), - _ => None, - }).collect::>() + pub fn private_routes(&self) -> Vec<&PrivateRoute> { + find_all_extract!(self.known_tagged_fields(), TaggedField::PrivateRoute(ref x), x).collect() } pub fn amount_pico_btc(&self) -> Option { @@ -936,6 +1019,41 @@ impl Invoice { Ok(()) } + /// Check that feature bits are set as required + fn check_feature_bits(&self) -> Result<(), SemanticError> { + // "If the payment_secret feature is set, MUST include exactly one s field." + let payment_secret_count = self.tagged_fields().filter(|&tf| match *tf { + TaggedField::PaymentSecret(_) => true, + _ => false, + }).count(); + if payment_secret_count > 1 { + return Err(SemanticError::MultiplePaymentSecrets); + } + + // "A writer MUST set an s field if and only if the payment_secret feature is set." + let has_payment_secret = payment_secret_count == 1; + let features = self.tagged_fields().find(|&tf| match *tf { + TaggedField::Features(_) => true, + _ => false, + }); + match features { + None if has_payment_secret => Err(SemanticError::InvalidFeatures), + None => Ok(()), + Some(TaggedField::Features(features)) => { + if features.supports_payment_secret() && has_payment_secret { + Ok(()) + } else if has_payment_secret { + Err(SemanticError::InvalidFeatures) + } else if features.supports_payment_secret() { + Err(SemanticError::InvalidFeatures) + } else { + Ok(()) + } + }, + Some(_) => unreachable!(), + } + } + /// Check that the invoice is signed correctly and that key recovery works pub fn check_signature(&self) -> Result<(), SemanticError> { match self.signed_invoice.recover_payee_pub_key() { @@ -952,7 +1070,7 @@ impl Invoice { Ok(()) } - /// Constructs an `Invoice` from a `SignedInvoice` by checking all its invariants. + /// Constructs an `Invoice` from a `SignedRawInvoice` by checking all its invariants. /// ``` /// use lightning_invoice::*; /// @@ -970,6 +1088,7 @@ impl Invoice { signed_invoice: signed_invoice, }; invoice.check_field_counts()?; + invoice.check_feature_bits()?; invoice.check_signature()?; Ok(invoice) @@ -981,6 +1100,8 @@ 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 pub fn tagged_fields(&self) -> FilterMap, fn(&RawTaggedField) -> Option<&TaggedField>> { self.signed_invoice.raw_invoice().known_tagged_fields() @@ -992,6 +1113,8 @@ impl Invoice { } /// Return the description or a hash of it for longer ones + /// + /// (C-not exported) because we don't yet export InvoiceDescription pub fn description(&self) -> InvoiceDescription { if let Some(ref direct) = self.signed_invoice.description() { return InvoiceDescription::Direct(direct); @@ -1021,26 +1144,38 @@ impl Invoice { self.signed_invoice.recover_payee_pub_key().expect("was checked by constructor").0 } - /// Returns the invoice's expiry time if present + /// Returns the invoice's expiry time, if present, otherwise [`DEFAULT_EXPIRY_TIME`]. pub fn expiry_time(&self) -> Duration { self.signed_invoice.expiry_time() .map(|x| x.0) - .unwrap_or(Duration::from_secs(3600)) + .unwrap_or(Duration::from_secs(DEFAULT_EXPIRY_TIME)) } - /// Returns the invoice's `min_cltv_expiry` time if present - pub fn min_final_cltv_expiry(&self) -> Option { - self.signed_invoice.min_final_cltv_expiry().map(|x| x.0) + /// 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() + .map(|x| x.0) + .unwrap_or(DEFAULT_MIN_FINAL_CLTV_EXPIRY) } /// Returns a list of all fallback addresses + /// + /// (C-not exported) as we don't support Vec<&NonOpaqueType> pub fn fallbacks(&self) -> Vec<&Fallback> { self.signed_invoice.fallbacks() } /// Returns a list of all routes included in the invoice - pub fn routes(&self) -> Vec<&RouteHint> { - self.signed_invoice.routes() + pub fn private_routes(&self) -> Vec<&PrivateRoute> { + self.signed_invoice.private_routes() + } + + /// Returns a list of all routes included in the invoice as the underlying hints + pub fn route_hints(&self) -> Vec<&RouteHint> { + find_all_extract!( + self.signed_invoice.known_tagged_fields(), TaggedField::PrivateRoute(ref x), x + ).map(|route| &**route).collect() } /// Returns the currency for which the invoice was issued @@ -1071,7 +1206,7 @@ impl TaggedField { TaggedField::ExpiryTime(_) => constants::TAG_EXPIRY_TIME, TaggedField::MinFinalCltvExpiry(_) => constants::TAG_MIN_FINAL_CLTV_EXPIRY, TaggedField::Fallback(_) => constants::TAG_FALLBACK, - TaggedField::Route(_) => constants::TAG_ROUTE, + TaggedField::PrivateRoute(_) => constants::TAG_PRIVATE_ROUTE, TaggedField::PaymentSecret(_) => constants::TAG_PAYMENT_SECRET, TaggedField::Features(_) => constants::TAG_FEATURES, }; @@ -1162,37 +1297,37 @@ impl ExpiryTime { } } -impl RouteHint { - /// Create a new (partial) route from a list of hops - pub fn new(hops: Vec) -> Result { - if hops.len() <= 12 { - Ok(RouteHint(hops)) +impl PrivateRoute { + /// Creates a new (partial) route from a list of hops + pub fn new(hops: RouteHint) -> Result { + if hops.0.len() <= 12 { + Ok(PrivateRoute(hops)) } else { Err(CreationError::RouteTooLong) } } - /// Returrn the underlying vector of hops - pub fn into_inner(self) -> Vec { + /// Returns the underlying list of hops + pub fn into_inner(self) -> RouteHint { self.0 } } -impl Into> for RouteHint { - fn into(self) -> Vec { +impl Into for PrivateRoute { + fn into(self) -> RouteHint { self.into_inner() } } -impl Deref for RouteHint { - type Target = Vec; +impl Deref for PrivateRoute { + type Target = RouteHint; - fn deref(&self) -> &Vec { + fn deref(&self) -> &RouteHint { &self.0 } } -impl Deref for Signature { +impl Deref for InvoiceSignature { type Target = RecoverableSignature; fn deref(&self) -> &RecoverableSignature { @@ -1253,6 +1388,12 @@ pub enum SemanticError { /// The invoice contains multiple descriptions and/or description hashes which isn't allowed MultipleDescriptions, + /// The invoice contains multiple payment secrets + MultiplePaymentSecrets, + + /// The invoice's features are invalid + InvalidFeatures, + /// The recovery id doesn't fit the signature/pub key InvalidRecoveryId, @@ -1267,6 +1408,8 @@ impl Display for SemanticError { SemanticError::MultiplePaymentHashes => f.write_str("The invoice has multiple payment hashes which isn't allowed"), SemanticError::NoDescription => f.write_str("No description or description hash are part of the invoice"), SemanticError::MultipleDescriptions => f.write_str("The invoice contains multiple descriptions and/or description hashes which isn't allowed"), + SemanticError::MultiplePaymentSecrets => f.write_str("The invoice contains multiple payment secrets"), + SemanticError::InvalidFeatures => f.write_str("The invoice's features are invalid"), SemanticError::InvalidRecoveryId => f.write_str("The recovery id doesn't fit the signature/pub key"), SemanticError::InvalidSignature => f.write_str("The invoice's signature is invalid"), } @@ -1278,7 +1421,7 @@ impl std::error::Error for SemanticError { } /// When signing using a fallible method either an user-supplied `SignError` or a `CreationError` /// may occur. #[derive(Eq, PartialEq, Debug, Clone)] -pub enum SignOrCreationError { +pub enum SignOrCreationError { /// An error occurred during signing SignError(S), @@ -1354,7 +1497,7 @@ mod test { use secp256k1::Secp256k1; use secp256k1::recovery::{RecoveryId, RecoverableSignature}; use secp256k1::key::{SecretKey, PublicKey}; - use {SignedRawInvoice, Signature, RawInvoice, RawHrp, RawDataPart, Currency, Sha256, + use {SignedRawInvoice, InvoiceSignature, RawInvoice, RawHrp, RawDataPart, Currency, Sha256, PositiveTimestamp}; let invoice = SignedRawInvoice { @@ -1383,7 +1526,7 @@ mod test { 0x7b, 0x1d, 0x85, 0x8d, 0xb1, 0xd1, 0xf7, 0xab, 0x71, 0x37, 0xdc, 0xb7, 0x83, 0x5d, 0xb2, 0xec, 0xd5, 0x18, 0xe1, 0xc9 ], - signature: Signature(RecoverableSignature::from_compact( + signature: InvoiceSignature(RecoverableSignature::from_compact( & [ 0x38u8, 0xec, 0x68, 0x91, 0x34, 0x5e, 0x20, 0x41, 0x45, 0xbe, 0x8a, 0x3a, 0x99, 0xde, 0x38, 0xe9, 0x8a, 0x39, 0xd6, 0xa5, 0x69, 0x43, @@ -1417,6 +1560,97 @@ mod test { assert!(new_signed.check_signature()); } + #[test] + fn test_check_feature_bits() { + use TaggedField::*; + use lightning::ln::features::InvoiceFeatures; + use secp256k1::Secp256k1; + use secp256k1::key::SecretKey; + use {RawInvoice, RawHrp, RawDataPart, Currency, Sha256, PositiveTimestamp, Invoice, + SemanticError}; + + let private_key = SecretKey::from_slice(&[42; 32]).unwrap(); + let payment_secret = lightning::ln::PaymentSecret([21; 32]); + let invoice_template = RawInvoice { + hrp: RawHrp { + currency: Currency::Bitcoin, + raw_amount: None, + si_prefix: None, + }, + data: RawDataPart { + timestamp: PositiveTimestamp::from_unix_timestamp(1496314658).unwrap(), + tagged_fields: vec ! [ + PaymentHash(Sha256(sha256::Hash::from_hex( + "0001020304050607080900010203040506070809000102030405060708090102" + ).unwrap())).into(), + Description( + ::Description::new( + "Please consider supporting this project".to_owned() + ).unwrap() + ).into(), + ], + }, + }; + + // Missing features + let invoice = { + let mut invoice = invoice_template.clone(); + invoice.data.tagged_fields.push(PaymentSecret(payment_secret).into()); + invoice.sign::<_, ()>(|hash| Ok(Secp256k1::new().sign_recoverable(hash, &private_key))) + }.unwrap(); + assert_eq!(Invoice::from_signed(invoice), Err(SemanticError::InvalidFeatures)); + + // Missing 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::empty()).into()); + invoice.sign::<_, ()>(|hash| Ok(Secp256k1::new().sign_recoverable(hash, &private_key))) + }.unwrap(); + assert_eq!(Invoice::from_signed(invoice), Err(SemanticError::InvalidFeatures)); + + // 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.sign::<_, ()>(|hash| Ok(Secp256k1::new().sign_recoverable(hash, &private_key))) + }.unwrap(); + assert!(Invoice::from_signed(invoice).is_ok()); + + // No payment secret or features + let invoice = { + let invoice = invoice_template.clone(); + invoice.sign::<_, ()>(|hash| Ok(Secp256k1::new().sign_recoverable(hash, &private_key))) + }.unwrap(); + assert!(Invoice::from_signed(invoice).is_ok()); + + // No payment secret or feature bits + let invoice = { + let mut invoice = invoice_template.clone(); + invoice.data.tagged_fields.push(Features(InvoiceFeatures::empty()).into()); + invoice.sign::<_, ()>(|hash| Ok(Secp256k1::new().sign_recoverable(hash, &private_key))) + }.unwrap(); + assert!(Invoice::from_signed(invoice).is_ok()); + + // Missing payment secret + let invoice = { + let mut invoice = invoice_template.clone(); + invoice.data.tagged_fields.push(Features(InvoiceFeatures::known()).into()); + invoice.sign::<_, ()>(|hash| Ok(Secp256k1::new().sign_recoverable(hash, &private_key))) + }.unwrap(); + assert_eq!(Invoice::from_signed(invoice), Err(SemanticError::InvalidFeatures)); + + // Multiple payment secrets + let invoice = { + let mut invoice = invoice_template.clone(); + 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_recoverable(hash, &private_key))) + }.unwrap(); + assert_eq!(Invoice::from_signed(invoice), Err(SemanticError::MultiplePaymentSecrets)); + } + #[test] fn test_builder_amount() { use ::*; @@ -1447,12 +1681,14 @@ mod test { #[test] fn test_builder_fail() { use ::*; + use lightning::routing::router::RouteHintHop; use std::iter::FromIterator; use secp256k1::key::PublicKey; let builder = InvoiceBuilder::new(Currency::Bitcoin) .payment_hash(sha256::Hash::from_slice(&[0;32][..]).unwrap()) - .current_timestamp(); + .current_timestamp() + .min_final_cltv_expiry(144); let too_long_string = String::from_iter( (0..1024).map(|_| '?') @@ -1480,10 +1716,10 @@ mod test { htlc_minimum_msat: None, htlc_maximum_msat: None, }; - let too_long_route = vec![route_hop; 13]; + let too_long_route = RouteHint(vec![route_hop; 13]); let long_route_res = builder.clone() .description("Test".into()) - .route(too_long_route) + .private_route(too_long_route) .build_raw(); assert_eq!(long_route_res, Err(CreationError::RouteTooLong)); @@ -1498,6 +1734,7 @@ mod test { #[test] fn test_builder_ok() { use ::*; + use lightning::routing::router::RouteHintHop; use secp256k1::Secp256k1; use secp256k1::key::{SecretKey, PublicKey}; use std::time::{UNIX_EPOCH, Duration}; @@ -1513,7 +1750,7 @@ mod test { ).unwrap(); let public_key = PublicKey::from_secret_key(&secp_ctx, &private_key); - let route_1 = vec![ + let route_1 = RouteHint(vec![ RouteHintHop { src_node_id: public_key.clone(), short_channel_id: de::parse_int_be(&[123; 8], 256).expect("short chan ID slice too big?"), @@ -1536,9 +1773,9 @@ mod test { htlc_minimum_msat: None, htlc_maximum_msat: None, } - ]; + ]); - let route_2 = vec![ + let route_2 = RouteHint(vec![ RouteHintHop { src_node_id: public_key.clone(), short_channel_id: 0, @@ -1561,7 +1798,7 @@ mod test { htlc_minimum_msat: None, htlc_maximum_msat: None, } - ]; + ]); let builder = InvoiceBuilder::new(Currency::BitcoinTestnet) .amount_pico_btc(123) @@ -1569,19 +1806,20 @@ mod test { .payee_pub_key(public_key.clone()) .expiry_time(Duration::from_secs(54321)) .min_final_cltv_expiry(144) - .min_final_cltv_expiry(143) .fallback(Fallback::PubKeyHash([0;20])) - .route(route_1.clone()) - .route(route_2.clone()) + .private_route(route_1.clone()) + .private_route(route_2.clone()) .description_hash(sha256::Hash::from_slice(&[3;32][..]).unwrap()) - .payment_hash(sha256::Hash::from_slice(&[21;32][..]).unwrap()); + .payment_hash(sha256::Hash::from_slice(&[21;32][..]).unwrap()) + .payment_secret(PaymentSecret([42; 32])) + .basic_mpp(); let invoice = builder.clone().build_signed(|hash| { secp_ctx.sign_recoverable(hash, &private_key) }).unwrap(); assert!(invoice.check_signature().is_ok()); - assert_eq!(invoice.tagged_fields().count(), 9); + assert_eq!(invoice.tagged_fields().count(), 10); assert_eq!(invoice.amount_pico_btc(), Some(123)); assert_eq!(invoice.currency(), Currency::BitcoinTestnet); @@ -1591,16 +1829,42 @@ 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(), Some(144)); + assert_eq!(invoice.min_final_cltv_expiry(), 144); assert_eq!(invoice.fallbacks(), vec![&Fallback::PubKeyHash([0;20])]); - assert_eq!(invoice.routes(), vec![&RouteHint(route_1), &RouteHint(route_2)]); + assert_eq!(invoice.private_routes(), vec![&PrivateRoute(route_1), &PrivateRoute(route_2)]); assert_eq!( invoice.description(), InvoiceDescription::Hash(&Sha256(sha256::Hash::from_slice(&[3;32][..]).unwrap())) ); assert_eq!(invoice.payment_hash(), &sha256::Hash::from_slice(&[21;32][..]).unwrap()); + assert_eq!(invoice.payment_secret(), Some(&PaymentSecret([42; 32]))); + assert_eq!(invoice.features(), Some(&InvoiceFeatures::known())); let raw_invoice = builder.build_raw().unwrap(); assert_eq!(raw_invoice, *invoice.into_signed_raw().raw_invoice()) } + + #[test] + fn test_default_values() { + use ::*; + use secp256k1::Secp256k1; + use secp256k1::key::SecretKey; + + let signed_invoice = InvoiceBuilder::new(Currency::Bitcoin) + .description("Test".into()) + .payment_hash(sha256::Hash::from_slice(&[0;32][..]).unwrap()) + .current_timestamp() + .build_raw() + .unwrap() + .sign::<_, ()>(|hash| { + let privkey = SecretKey::from_slice(&[41; 32]).unwrap(); + let secp_ctx = Secp256k1::new(); + Ok(secp_ctx.sign_recoverable(hash, &privkey)) + }) + .unwrap(); + let invoice = Invoice::from_signed(signed_invoice).unwrap(); + + assert_eq!(invoice.min_final_cltv_expiry(), DEFAULT_MIN_FINAL_CLTV_EXPIRY); + assert_eq!(invoice.expiry_time(), Duration::from_secs(DEFAULT_EXPIRY_TIME)); + } }