X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning-invoice%2Fsrc%2Flib.rs;h=2ce58f296f9b26b06ec4c92d8b634a57f680d5b3;hb=99ecd02f7d5dbf1d53eb59b7e3ef90e87441193d;hp=9535886d97367cc84f0a3792fbfaa871e1667f4e;hpb=246493f30002a05684d98c4fbe59498987e7dea0;p=rust-lightning diff --git a/lightning-invoice/src/lib.rs b/lightning-invoice/src/lib.rs index 9535886d..2ce58f29 100644 --- a/lightning-invoice/src/lib.rs +++ b/lightning-invoice/src/lib.rs @@ -30,7 +30,7 @@ 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}; @@ -60,12 +60,16 @@ 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; +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 -const DEFAULT_MIN_FINAL_CLTV_EXPIRY: u64 = 18; +/// [`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 @@ -168,7 +172,7 @@ pub fn check_platform() { /// /// (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, @@ -180,6 +184,7 @@ pub struct InvoiceBuilder { 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. @@ -327,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 @@ -343,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 { @@ -353,7 +366,7 @@ pub enum TaggedField { ExpiryTime(ExpiryTime), MinFinalCltvExpiry(MinFinalCltvExpiry), Fallback(Fallback), - Route(RouteHint), + PrivateRoute(PrivateRoute), PaymentSecret(PaymentSecret), Features(InvoiceFeatures), } @@ -410,7 +423,7 @@ pub struct InvoiceSignature(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)] @@ -422,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 { @@ -443,14 +456,15 @@ impl InvoiceBuilder { 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, @@ -462,6 +476,7 @@ impl InvoiceBuilder InvoiceBuilder 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) { @@ -504,23 +513,16 @@ impl InvoiceBuilder) -> 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 { @@ -553,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), @@ -564,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), @@ -590,22 +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 { + 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 { +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. @@ -644,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) } @@ -722,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::* /// @@ -736,11 +765,35 @@ 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() + }) }; } @@ -861,17 +914,11 @@ impl RawInvoice { /// (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 { @@ -972,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() { @@ -988,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::*; /// @@ -1006,6 +1088,7 @@ impl Invoice { signed_invoice: signed_invoice, }; invoice.check_field_counts()?; + invoice.check_feature_bits()?; invoice.check_signature()?; Ok(invoice) @@ -1084,8 +1167,15 @@ impl Invoice { } /// 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 @@ -1116,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, }; @@ -1207,32 +1297,32 @@ 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 } } @@ -1298,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, @@ -1312,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"), } @@ -1462,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 ::*; @@ -1492,6 +1681,7 @@ mod test { #[test] fn test_builder_fail() { use ::*; + use lightning::routing::router::RouteHintHop; use std::iter::FromIterator; use secp256k1::key::PublicKey; @@ -1526,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)); @@ -1544,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}; @@ -1559,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?"), @@ -1582,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, @@ -1607,7 +1798,7 @@ mod test { htlc_minimum_msat: None, htlc_maximum_msat: None, } - ]; + ]); let builder = InvoiceBuilder::new(Currency::BitcoinTestnet) .amount_pico_btc(123) @@ -1616,17 +1807,19 @@ mod test { .expiry_time(Duration::from_secs(54321)) .min_final_cltv_expiry(144) .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(), 8); + assert_eq!(invoice.tagged_fields().count(), 10); assert_eq!(invoice.amount_pico_btc(), Some(123)); assert_eq!(invoice.currency(), Currency::BitcoinTestnet); @@ -1638,12 +1831,14 @@ mod test { 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.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())