X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning-invoice%2Fsrc%2Flib.rs;h=11406a4644dd0f0e0c5133dc89cf8c84c55f7bbb;hb=b5f0ebab77961ce243fcf5a5fad3d4e161f4952a;hp=e9ca442f2b2006ea5ff343a0d4785ce7dc945899;hpb=c3d25ed4bdca38c747ea78f234f0fc57e82d8cf8;p=rust-lightning diff --git a/lightning-invoice/src/lib.rs b/lightning-invoice/src/lib.rs index e9ca442f..11406a46 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,25 +15,32 @@ //! * 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; +extern crate lightning; extern crate num_traits; 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 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; @@ -49,6 +57,16 @@ 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 +const DEFAULT_EXPIRY_TIME: u64 = 3600; + +/// Default minimum final CLTV expiry as defined by [BOLT 11]. +/// +/// [BOLT 11]: https://github.com/lightningnetwork/lightning-rfc/blob/master/11-payment-encoding.md +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, @@ -131,6 +149,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) /// }) @@ -146,8 +165,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, @@ -158,6 +179,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. @@ -173,6 +196,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 @@ -202,7 +228,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, @@ -220,6 +246,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 @@ -278,6 +306,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]; @@ -323,8 +354,9 @@ pub enum TaggedField { ExpiryTime(ExpiryTime), MinFinalCltvExpiry(MinFinalCltvExpiry), Fallback(Fallback), - Route(Route), + Route(RouteHint), PaymentSecret(PaymentSecret), + Features(InvoiceFeatures), } /// SHA-256 hash @@ -342,10 +374,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 /// @@ -375,7 +403,7 @@ pub enum Fallback { /// Recoverable signature #[derive(Eq, PartialEq, Debug, Clone)] -pub struct Signature(pub RecoverableSignature); +pub struct InvoiceSignature(pub RecoverableSignature); /// Private routing information /// @@ -383,26 +411,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 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, -} +pub struct RouteHint(Vec); /// Tag constants as specified in BOLT11 #[allow(missing_docs)] @@ -416,9 +425,10 @@ pub mod constants { pub const TAG_FALLBACK: u8 = 9; pub const TAG_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 { @@ -433,14 +443,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, @@ -451,6 +463,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, } } @@ -471,12 +485,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) { @@ -486,12 +494,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)); @@ -499,8 +501,8 @@ impl InvoiceBuilder { } /// Adds a private route. - pub fn route(mut self, route: Vec) -> Self { - match Route::new(route) { + pub fn route(mut self, route: Vec) -> Self { + match RouteHint::new(route) { Ok(r) => self.tagged_fields.push(TaggedField::Route(r)), Err(e) => self.error = Some(e), } @@ -508,7 +510,7 @@ impl InvoiceBuilder { } } -impl InvoiceBuilder { +impl InvoiceBuilder { /// Builds a `RawInvoice` if no `CreationError` occurred while construction any of the fields. pub fn build_raw(self) -> Result { @@ -541,9 +543,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), @@ -552,23 +554,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), @@ -578,14 +580,34 @@ 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 { /// 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. @@ -624,6 +646,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) } @@ -635,7 +658,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) } @@ -649,8 +672,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 } @@ -726,8 +749,8 @@ 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] { + /// 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); @@ -746,7 +769,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 @@ -765,6 +793,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 { @@ -776,11 +807,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>> { @@ -825,6 +858,11 @@ impl RawInvoice { find_extract!(self.known_tagged_fields(), TaggedField::PaymentSecret(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> pub fn fallbacks(&self) -> Vec<&Fallback> { self.known_tagged_fields().filter_map(|tf| match tf { &TaggedField::Fallback(ref f) => Some(f), @@ -832,11 +870,11 @@ impl RawInvoice { }).collect::>() } - pub fn routes(&self) -> Vec<&Route> { + pub fn routes(&self) -> Vec<&RouteHint> { self.known_tagged_fields().filter_map(|tf| match tf { &TaggedField::Route(ref r) => Some(r), _ => None, - }).collect::>() + }).collect::>() } pub fn amount_pico_btc(&self) -> Option { @@ -937,6 +975,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() { @@ -971,6 +1044,7 @@ impl Invoice { signed_invoice: signed_invoice, }; invoice.check_field_counts()?; + invoice.check_feature_bits()?; invoice.check_signature()?; Ok(invoice) @@ -982,6 +1056,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() @@ -993,6 +1069,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); @@ -1007,35 +1085,45 @@ impl Invoice { self.signed_invoice.payee_pub_key().map(|x| &x.0) } - /// Get the payment secret if one was included in the invoice - pub fn payment_secret(&self) -> Option<&PaymentSecret> { - self.signed_invoice.payment_secret() - } + /// Get the payment secret if one was included in the invoice + pub fn payment_secret(&self) -> Option<&PaymentSecret> { + self.signed_invoice.payment_secret() + } + + /// Get the invoice features if they were included in the invoice + pub fn features(&self) -> Option<&InvoiceFeatures> { + self.signed_invoice.features() + } /// Recover the payee's public key (only to be used if none was included in the invoice) pub fn recover_payee_pub_key(&self) -> PublicKey { 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<&u64> { - 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<&Route> { + pub fn routes(&self) -> Vec<&RouteHint> { self.signed_invoice.routes() } @@ -1069,6 +1157,7 @@ impl TaggedField { TaggedField::Fallback(_) => constants::TAG_FALLBACK, TaggedField::Route(_) => constants::TAG_ROUTE, TaggedField::PaymentSecret(_) => constants::TAG_PAYMENT_SECRET, + TaggedField::Features(_) => constants::TAG_FEATURES, }; u5::try_from_u8(tag).expect("all tags defined are <32") @@ -1157,37 +1246,37 @@ impl ExpiryTime { } } -impl Route { +impl RouteHint { /// Create a new (partial) route from a list of hops - pub fn new(hops: Vec) -> Result { + pub fn new(hops: Vec) -> Result { if hops.len() <= 12 { - Ok(Route(hops)) + Ok(RouteHint(hops)) } else { Err(CreationError::RouteTooLong) } } /// Returrn the underlying vector of hops - pub fn into_inner(self) -> Vec { + pub fn into_inner(self) -> Vec { self.0 } } -impl Into> for Route { - fn into(self) -> Vec { +impl Into> for RouteHint { + fn into(self) -> Vec { self.into_inner() } } -impl Deref for Route { - type Target = Vec; +impl Deref for RouteHint { + type Target = Vec; - fn deref(&self) -> &Vec { + fn deref(&self) -> &Vec { &self.0 } } -impl Deref for Signature { +impl Deref for InvoiceSignature { type Target = RecoverableSignature; fn deref(&self) -> &RecoverableSignature { @@ -1248,6 +1337,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, @@ -1262,6 +1357,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"), } @@ -1272,6 +1369,8 @@ impl std::error::Error for SemanticError { } /// When signing using a fallible method either an user-supplied `SignError` or a `CreationError` /// may occur. +/// +/// (C-not exported) As we don't support unbounded generics #[derive(Eq, PartialEq, Debug, Clone)] pub enum SignOrCreationError { /// An error occurred during signing @@ -1349,7 +1448,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 { @@ -1378,7 +1477,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, @@ -1447,7 +1546,8 @@ mod test { 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(|_| '?') @@ -1458,18 +1558,22 @@ mod test { .build_raw(); assert_eq!(long_desc_res, Err(CreationError::DescriptionTooLong)); - let route_hop = RouteHop { - pubkey: PublicKey::from_slice( + let route_hop = RouteHintHop { + src_node_id: PublicKey::from_slice( &[ 0x03, 0x9e, 0x03, 0xa9, 0x01, 0xb8, 0x55, 0x34, 0xff, 0x1e, 0x92, 0xc4, 0x3c, 0x74, 0x43, 0x1f, 0x7c, 0xe7, 0x20, 0x46, 0x06, 0x0f, 0xcf, 0x7a, 0x95, 0xc3, 0x7e, 0x14, 0x8f, 0x78, 0xc7, 0x72, 0x55 ][..] ).unwrap(), - short_channel_id: [0; 8], - fee_base_msat: 0, - fee_proportional_millionths: 0, + short_channel_id: 0, + fees: RoutingFees { + base_msat: 0, + proportional_millionths: 0, + }, cltv_expiry_delta: 0, + htlc_minimum_msat: None, + htlc_maximum_msat: None, }; let too_long_route = vec![route_hop; 13]; let long_route_res = builder.clone() @@ -1505,36 +1609,52 @@ mod test { let public_key = PublicKey::from_secret_key(&secp_ctx, &private_key); let route_1 = vec![ - RouteHop { - pubkey: public_key.clone(), - short_channel_id: [123; 8], - fee_base_msat: 2, - fee_proportional_millionths: 1, + RouteHintHop { + src_node_id: public_key.clone(), + short_channel_id: de::parse_int_be(&[123; 8], 256).expect("short chan ID slice too big?"), + fees: RoutingFees { + base_msat: 2, + proportional_millionths: 1, + }, cltv_expiry_delta: 145, + htlc_minimum_msat: None, + htlc_maximum_msat: None, }, - RouteHop { - pubkey: public_key.clone(), - short_channel_id: [42; 8], - fee_base_msat: 3, - fee_proportional_millionths: 2, + RouteHintHop { + src_node_id: public_key.clone(), + short_channel_id: de::parse_int_be(&[42; 8], 256).expect("short chan ID slice too big?"), + fees: RoutingFees { + base_msat: 3, + proportional_millionths: 2, + }, cltv_expiry_delta: 146, + htlc_minimum_msat: None, + htlc_maximum_msat: None, } ]; let route_2 = vec![ - RouteHop { - pubkey: public_key.clone(), - short_channel_id: [0; 8], - fee_base_msat: 4, - fee_proportional_millionths: 3, + RouteHintHop { + src_node_id: public_key.clone(), + short_channel_id: 0, + fees: RoutingFees { + base_msat: 4, + proportional_millionths: 3, + }, cltv_expiry_delta: 147, + htlc_minimum_msat: None, + htlc_maximum_msat: None, }, - RouteHop { - pubkey: public_key.clone(), - short_channel_id: [1; 8], - fee_base_msat: 5, - fee_proportional_millionths: 4, + RouteHintHop { + src_node_id: public_key.clone(), + short_channel_id: de::parse_int_be(&[1; 8], 256).expect("short chan ID slice too big?"), + fees: RoutingFees { + base_msat: 5, + proportional_millionths: 4, + }, cltv_expiry_delta: 148, + htlc_minimum_msat: None, + htlc_maximum_msat: None, } ]; @@ -1544,7 +1664,6 @@ 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()) @@ -1556,7 +1675,7 @@ mod test { }).unwrap(); assert!(invoice.check_signature().is_ok()); - assert_eq!(invoice.tagged_fields().count(), 9); + assert_eq!(invoice.tagged_fields().count(), 8); assert_eq!(invoice.amount_pico_btc(), Some(123)); assert_eq!(invoice.currency(), Currency::BitcoinTestnet); @@ -1566,9 +1685,9 @@ 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![&Route(route_1), &Route(route_2)]); + assert_eq!(invoice.routes(), vec![&RouteHint(route_1), &RouteHint(route_2)]); assert_eq!( invoice.description(), InvoiceDescription::Hash(&Sha256(sha256::Hash::from_slice(&[3;32][..]).unwrap())) @@ -1578,4 +1697,28 @@ mod test { 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)); + } }