From 6e7fa21700bfb42bf20dce1bcc09b404150fa44b Mon Sep 17 00:00:00 2001 From: Sebastian Geisler Date: Fri, 2 Nov 2018 18:15:40 -0700 Subject: [PATCH 1/2] Make linter and clippy as happy as possible while staying compatible to 1.14.0 --- src/de.rs | 7 +------ src/lib.rs | 46 ++++++++++++++++------------------------------ src/ser.rs | 6 +++--- 3 files changed, 20 insertions(+), 39 deletions(-) diff --git a/src/de.rs b/src/de.rs index 48a05149..4847eefe 100644 --- a/src/de.rs +++ b/src/de.rs @@ -80,11 +80,7 @@ mod hrp_sm { } fn is_final(&self) -> bool { - if *self == States::ParseL || *self == States::ParseN { - false - } else { - true - } + !(*self == States::ParseL || *self == States::ParseN) } } @@ -710,7 +706,6 @@ mod test { use de::ParseError; use secp256k1::{PublicKey, Secp256k1}; use bech32::u5; - use SignedRawInvoice; use bitcoin_hashes::hex::FromHex; use bitcoin_hashes::sha256::Sha256Hash; diff --git a/src/lib.rs b/src/lib.rs index 4de7f6ae..9c2b19f2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,8 +37,8 @@ const MAX_EXPIRY_TIME: u64 = 60 * 60 * 24 * 356; /// please open an issue. If all tests pass you should be able to use this library safely by just /// removing this function till we patch it accordingly. fn __system_time_size_check() { - /// Use 2 * sizeof(u64) as expected size since the expected underlying implementation is storing - /// a `Duration` since `SystemTime::UNIX_EPOCH`. + // Use 2 * sizeof(u64) as expected size since the expected underlying implementation is storing + // a `Duration` since `SystemTime::UNIX_EPOCH`. unsafe { std::mem::transmute::(UNIX_EPOCH); } } @@ -246,11 +246,11 @@ impl SiPrefix { /// Returns the multiplier to go from a BTC value to picoBTC implied by this SiPrefix. /// This is effectively 10^12 * the prefix multiplier pub fn multiplier(&self) -> u64 { - match self { - &SiPrefix::Milli => 1_000_000_000, - &SiPrefix::Micro => 1_000_000, - &SiPrefix::Nano => 1_000, - &SiPrefix::Pico => 1, + match *self { + SiPrefix::Milli => 1_000_000_000, + SiPrefix::Micro => 1_000_000, + SiPrefix::Nano => 1_000, + SiPrefix::Pico => 1, } } @@ -354,8 +354,6 @@ pub struct RouteHop { } pub mod constants { - use bech32::u5; - pub const TAG_PAYMENT_HASH: u8 = 1; pub const TAG_DESCRIPTION: u8 = 13; pub const TAG_PAYEE_PUB_KEY: u8 = 19; @@ -366,17 +364,6 @@ pub mod constants { pub const TAG_ROUTE: u8 = 3; } -/// FOR INTERNAL USE ONLY! READ BELOW! -/// -/// It's a convenience function to convert `u8` tags to `u5` tags. Therefore `tag` has to -/// be in range `[0..32]`. -/// -/// # Panics -/// If the `tag` value is not in the range `[0..32]`. -fn as_u5(tag: u8) -> u5 { - u5::try_from_u8(tag).unwrap() -} - impl InvoiceBuilder { /// Construct new, empty `InvoiceBuilder`. All necessary fields have to be filled first before /// `InvoiceBuilder::build(self)` becomes available. @@ -633,7 +620,7 @@ impl SignedRawInvoice { recovered_pub_key = Some(recovered); } - let pub_key = included_pub_key.or(recovered_pub_key.as_ref()) + let pub_key = included_pub_key.or_else(|| recovered_pub_key.as_ref()) .expect("One is always present"); let hash = Message::from_slice(&self.hash[..]) @@ -671,8 +658,8 @@ impl SignedRawInvoice { /// ``` macro_rules! find_extract { ($iter:expr, $enm:pat, $enm_var:ident) => { - $iter.filter_map(|tf| match tf { - &$enm => Some($enm_var), + $iter.filter_map(|tf| match *tf { + $enm => Some($enm_var), _ => None, }).next() }; @@ -741,8 +728,8 @@ impl RawInvoice { // function's type signature. // TODO: refactor once impl Trait is available fn match_raw(raw: &RawTaggedField) -> Option<&TaggedField> { - match raw { - &RawTaggedField::KnownSemantics(ref tf) => Some(tf), + match *raw { + RawTaggedField::KnownSemantics(ref tf) => Some(tf), _ => None, } } @@ -777,14 +764,14 @@ impl RawInvoice { pub fn fallbacks(&self) -> Vec<&Fallback> { self.known_tagged_fields().filter_map(|tf| match tf { &TaggedField::Fallback(ref f) => Some(f), - num_traits => None, + _ => None, }).collect::>() } pub fn routes(&self) -> Vec<&Route> { self.known_tagged_fields().filter_map(|tf| match tf { &TaggedField::Route(ref r) => Some(r), - num_traits => None, + _ => None, }).collect::>() } @@ -854,7 +841,7 @@ impl Deref for PositiveTimestamp { } impl Invoice { - fn into_signed_raw(self) -> SignedRawInvoice { + pub fn into_signed_raw(self) -> SignedRawInvoice { self.signed_invoice } @@ -1183,7 +1170,6 @@ mod test { #[test] fn test_calc_invoice_hash() { use ::{RawInvoice, RawHrp, RawDataPart, Currency, PositiveTimestamp}; - use secp256k1::*; use ::TaggedField::*; let invoice = RawInvoice { @@ -1222,7 +1208,7 @@ mod test { use {SignedRawInvoice, Signature, RawInvoice, RawHrp, RawDataPart, Currency, Sha256, PositiveTimestamp}; - let mut invoice = SignedRawInvoice { + let invoice = SignedRawInvoice { raw_invoice: RawInvoice { hrp: RawHrp { currency: Currency::Bitcoin, diff --git a/src/ser.rs b/src/ser.rs index 6e9f9d8d..379903b8 100644 --- a/src/ser.rs +++ b/src/ser.rs @@ -46,9 +46,9 @@ impl Display for RawHrp { impl Display for Currency { fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { - let currency_code = match self { - &Currency::Bitcoin => "bc", - &Currency::BitcoinTestnet => "tb", + let currency_code = match *self { + Currency::Bitcoin => "bc", + Currency::BitcoinTestnet => "tb", }; write!(f, "{}", currency_code) } From 41b35e1e40ebf1bb725e7bb1848364f1c1458ac9 Mon Sep 17 00:00:00 2001 From: Sebastian Geisler Date: Fri, 2 Nov 2018 19:33:03 -0700 Subject: [PATCH 2/2] Make docs mandatory and fix missing docs --- src/de.rs | 12 ++++++++ src/lib.rs | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 92 insertions(+), 2 deletions(-) diff --git a/src/de.rs b/src/de.rs index 4847eefe..2518df16 100644 --- a/src/de.rs +++ b/src/de.rs @@ -582,6 +582,9 @@ impl FromBase32 for Route { } } +/// 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)] pub enum ParseError { Bech32Error(bech32::Error), @@ -601,13 +604,22 @@ pub enum ParseError { InvalidScriptHashLength, InvalidRecoveryId, InvalidSliceLength(String), + + /// Not an error, but used internally to signal that a part of the invoice should be ignored + /// according to BOLT11 Skip, TimestampOverflow, } +/// 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)] pub enum ParseOrSemanticError { + /// The invoice couldn't be decoded ParseError(ParseError), + + /// The invoice could be decoded but violates the BOLT11 standard SemanticError(::SemanticError), } diff --git a/src/lib.rs b/src/lib.rs index 9c2b19f2..c2ddf762 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,20 @@ +#![deny(missing_docs)] +#![deny(non_upper_case_globals)] +#![deny(non_camel_case_types)] +#![deny(non_snake_case)] +#![deny(unused_mut)] + +#![cfg_attr(feature = "strict", deny(warnings))] + +//! This crate provides data structures to represent +//! [lightning BOLT11](https://github.com/lightningnetwork/lightning-rfc/blob/master/11-payment-encoding.md) +//! 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 + extern crate bech32; extern crate bitcoin_hashes; extern crate num_traits; @@ -156,10 +173,15 @@ pub struct Invoice { signed_invoice: SignedRawInvoice, } +/// 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. #[derive(Eq, PartialEq, Debug, Clone)] pub enum InvoiceDescription<'f> { + /// Reference to the directly supplied description in the invoice Direct(&'f Description), - Hash(&'f Sha256) + + /// Reference to the description's hash included in the invoice + Hash(&'f Sha256), } /// Represents a signed `RawInvoice` with cached hash. The signature is not checked and may be @@ -188,6 +210,8 @@ pub struct SignedRawInvoice { /// 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. +/// +/// For methods without docs see the corresponding methods in `Invoice`. #[derive(Eq, PartialEq, Debug, Clone)] pub struct RawInvoice { /// human readable part @@ -263,6 +287,8 @@ impl SiPrefix { } } +/// Enum representing the crypto currencies supported by this library +#[allow(missing_docs)] #[derive(Eq, PartialEq, Debug, Clone)] pub enum Currency { Bitcoin, @@ -279,6 +305,9 @@ pub enum RawTaggedField { } /// Tagged field with known tag +/// +/// For descriptions of the enum values please refer to the enclosed type's docs. +#[allow(missing_docs)] #[derive(Eq, PartialEq, Debug, Clone)] pub enum TaggedField { PaymentHash(Sha256), @@ -322,6 +351,7 @@ pub struct MinFinalCltvExpiry(pub u64); // TODO: better types instead onf byte arrays /// Fallback address in case no LN payment is possible +#[allow(missing_docs)] #[derive(Eq, PartialEq, Debug, Clone)] pub enum Fallback { SegWitProgram { @@ -344,15 +374,27 @@ pub struct Signature(pub RecoverableSignature); #[derive(Eq, PartialEq, Debug, Clone)] pub struct Route(Vec); +/// Node on a private route #[derive(Eq, PartialEq, Debug, Clone)] pub struct RouteHop { + /// Node's public key pub pubkey: PublicKey, + + /// Which channel of this node we would be using pub short_channel_id: [u8; 8], + + /// Fee charged by this node per transaction pub fee_base_msat: u32, + + /// Fee charged by this node proportional to the amount routed pub fee_proportional_millionths: u32, + + /// Delta substracted by this node from incoming cltv_expiry value pub cltv_expiry_delta: u16, } +/// Tag constants as specified in BOLT11 +#[allow(missing_docs)] pub mod constants { pub const TAG_PAYMENT_HASH: u8 = 1; pub const TAG_DESCRIPTION: u8 = 13; @@ -665,6 +707,7 @@ macro_rules! find_extract { }; } +#[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] { @@ -841,6 +884,7 @@ impl Deref for PositiveTimestamp { } impl Invoice { + /// Transform the `Invoice` into it's unchecked version pub fn into_signed_raw(self) -> SignedRawInvoice { self.signed_invoice } @@ -911,6 +955,7 @@ impl Invoice { Ok(invoice) } + /// Returns the `Invoice`'s timestamp (should equal it's creation time) pub fn timestamp(&self) -> &SystemTime { self.signed_invoice.raw_invoice().data.timestamp.as_time() } @@ -921,10 +966,12 @@ impl Invoice { self.signed_invoice.raw_invoice().known_tagged_fields() } + /// Returns the hash to which we will receive the preimage on completion of the payment pub fn payment_hash(&self) -> &Sha256 { self.signed_invoice.payment_hash().expect("checked by constructor") } + /// Return the description or a hash of it for longer ones pub fn description(&self) -> InvoiceDescription { if let Some(ref direct) = self.signed_invoice.description() { return InvoiceDescription::Direct(direct); @@ -934,34 +981,42 @@ impl Invoice { unreachable!("ensured by constructor"); } + /// Get the payee's public key if one was included in the invoice pub fn payee_pub_key(&self) -> Option<&PayeePubKey> { self.signed_invoice.payee_pub_key() } + /// Recover the payee's public key (only to be used if none was included in the invoice) pub fn recover_payee_pub_key(&self) -> PayeePubKey { self.signed_invoice.recover_payee_pub_key().expect("was checked by constructor") } + /// Returns the invoice's expiry time if present pub fn expiry_time(&self) -> Option<&ExpiryTime> { self.signed_invoice.expiry_time() } + /// Returns the invoice's `min_cltv_expiry` time if present pub fn min_final_cltv_expiry(&self) -> Option<&MinFinalCltvExpiry> { self.signed_invoice.min_final_cltv_expiry() } + /// Returns a list of all fallback addresses 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<&Route> { self.signed_invoice.routes() } + /// Returns the currency for which the invoice was issued pub fn currency(&self) -> Currency { self.signed_invoice.currency() } + /// Returns the amount if specified in the invoice as pico . pub fn amount_pico_btc(&self) -> Option { self.signed_invoice.amount_pico_btc() } @@ -1005,6 +1060,7 @@ impl Description { } } + /// Returns the underlying description `String` pub fn into_inner(self) -> String { self.0 } @@ -1039,6 +1095,9 @@ impl Deref for PayeePubKey { } impl ExpiryTime { + /// Construct an `ExpiryTime` from seconds. If there exists a `PositiveTimestamp` which would + /// overflow on adding the `EpiryTime` to it then this function will return a + /// `CreationError::ExpiryTimeOutOfBounds`. pub fn from_seconds(seconds: u64) -> Result { if seconds <= MAX_EXPIRY_TIME { Ok(ExpiryTime(Duration::from_secs(seconds))) @@ -1047,6 +1106,9 @@ impl ExpiryTime { } } + /// Construct an `ExpiryTime` from a `Duration`. If there exists a `PositiveTimestamp` which + /// would overflow on adding the `EpiryTime` to it then this function will return a + /// `CreationError::ExpiryTimeOutOfBounds`. pub fn from_duration(duration: Duration) -> Result { if duration.as_secs() <= MAX_EXPIRY_TIME { Ok(ExpiryTime(duration)) @@ -1055,16 +1117,19 @@ impl ExpiryTime { } } + /// Returns the expiry time in seconds pub fn as_seconds(&self) -> u64 { self.0.as_secs() } + /// Returns a reference to the underlying `Duration` (=expiry time) pub fn as_duration(&self) -> &Duration { &self.0 } } impl Route { + /// Create a new (partial) route from a list of hops pub fn new(hops: Vec) -> Result { if hops.len() <= 12 { Ok(Route(hops)) @@ -1073,7 +1138,8 @@ impl Route { } } - fn into_inner(self) -> Vec { + /// Returrn the underlying vector of hops + pub fn into_inner(self) -> Vec { self.0 } } @@ -1128,13 +1194,22 @@ pub enum CreationError { /// requirements sections in BOLT #11 #[derive(Eq, PartialEq, Debug, Clone)] pub enum SemanticError { + /// The invoice is missing the mandatory payment hash NoPaymentHash, + + /// The invoice has multiple payment hashes which isn't allowed MultiplePaymentHashes, + /// No description or description hash are part of the invoice NoDescription, + + /// The invoice contains multiple descriptions and/or description hashes which isn't allowed MultipleDescriptions, + /// The recovery id doesn't fit the signature/pub key InvalidRecoveryId, + + /// The invoice's signature is invalid InvalidSignature, } @@ -1142,7 +1217,10 @@ pub enum SemanticError { /// may occur. #[derive(Eq, PartialEq, Debug, Clone)] pub enum SignOrCreationError { + /// An error occurred during signing SignError(S), + + /// An error occurred while building the transaction CreationError(CreationError), }