diff --git a/lightning-invoice/src/lib.rs b/lightning-invoice/src/lib.rs index e0ba4dda3..8177e835a 100644 --- a/lightning-invoice/src/lib.rs +++ b/lightning-invoice/src/lib.rs @@ -218,10 +218,13 @@ pub const DEFAULT_MIN_FINAL_CLTV_EXPIRY_DELTA: u64 = 18; /// * `D`: exactly one [`TaggedField::Description`] or [`TaggedField::DescriptionHash`] /// * `H`: exactly one [`TaggedField::PaymentHash`] /// * `T`: the timestamp is set +/// * `C`: the CLTV expiry is set +/// * `S`: the payment secret is set +/// * `M`: payment metadata is set /// /// This is not exported to bindings users as we likely need to manually select one set of boolean type parameters. #[derive(Eq, PartialEq, Debug, Clone)] -pub struct InvoiceBuilder { +pub struct InvoiceBuilder { currency: Currency, amount: Option, si_prefix: Option, @@ -234,6 +237,7 @@ pub struct InvoiceBuilder, phantom_c: core::marker::PhantomData, phantom_s: core::marker::PhantomData, + phantom_m: core::marker::PhantomData, } /// Represents a syntactically and semantically correct lightning BOLT11 invoice. @@ -488,7 +492,7 @@ pub mod constants { 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 { @@ -505,14 +509,15 @@ impl InvoiceBuilder { phantom_t: core::marker::PhantomData, phantom_c: core::marker::PhantomData, phantom_s: core::marker::PhantomData, + phantom_m: core::marker::PhantomData, } } } -impl InvoiceBuilder { +impl InvoiceBuilder { /// Helper function to set the completeness flags. - fn set_flags(self) -> InvoiceBuilder { - InvoiceBuilder:: { + fn set_flags(self) -> InvoiceBuilder { + InvoiceBuilder:: { currency: self.currency, amount: self.amount, si_prefix: self.si_prefix, @@ -525,6 +530,7 @@ impl InvoiceBui phantom_t: core::marker::PhantomData, phantom_c: core::marker::PhantomData, phantom_s: core::marker::PhantomData, + phantom_m: core::marker::PhantomData, } } @@ -569,7 +575,7 @@ impl InvoiceBui } } -impl InvoiceBuilder { +impl InvoiceBuilder { /// Builds a [`RawInvoice`] if no [`CreationError`] occurred while construction any of the /// fields. pub fn build_raw(self) -> Result { @@ -603,9 +609,9 @@ impl InvoiceBuilder 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), @@ -614,13 +620,13 @@ impl InvoiceBuilder InvoiceBuilder { + pub fn description_hash(mut self, description_hash: sha256::Hash) -> InvoiceBuilder { self.tagged_fields.push(TaggedField::DescriptionHash(Sha256(description_hash))); self.set_flags() } /// Set the description or description hash. This function is only available if no description (hash) was set. - pub fn invoice_description(self, description: InvoiceDescription) -> InvoiceBuilder { + pub fn invoice_description(self, description: InvoiceDescription) -> InvoiceBuilder { match description { InvoiceDescription::Direct(desc) => { self.description(desc.clone().into_inner()) @@ -632,18 +638,18 @@ impl InvoiceBuilder InvoiceBuilder { +impl InvoiceBuilder { /// Set the payment hash. This function is only available if no payment hash was set. - pub fn payment_hash(mut self, hash: sha256::Hash) -> InvoiceBuilder { + pub fn payment_hash(mut self, hash: sha256::Hash) -> InvoiceBuilder { self.tagged_fields.push(TaggedField::PaymentHash(Sha256(hash))); self.set_flags() } } -impl InvoiceBuilder { +impl InvoiceBuilder { /// Sets the timestamp to a specific [`SystemTime`]. #[cfg(feature = "std")] - pub fn timestamp(mut self, time: SystemTime) -> InvoiceBuilder { + pub fn timestamp(mut self, time: SystemTime) -> InvoiceBuilder { match PositiveTimestamp::from_system_time(time) { Ok(t) => self.timestamp = Some(t), Err(e) => self.error = Some(e), @@ -654,7 +660,7 @@ impl InvoiceBuilder InvoiceBuilder { + pub fn duration_since_epoch(mut self, time: Duration) -> InvoiceBuilder { match PositiveTimestamp::from_duration_since_epoch(time) { Ok(t) => self.timestamp = Some(t), Err(e) => self.error = Some(e), @@ -665,34 +671,82 @@ impl InvoiceBuilder InvoiceBuilder { + pub fn current_timestamp(mut self) -> InvoiceBuilder { let now = PositiveTimestamp::from_system_time(SystemTime::now()); self.timestamp = Some(now.expect("for the foreseeable future this shouldn't happen")); self.set_flags() } } -impl InvoiceBuilder { +impl InvoiceBuilder { /// Sets `min_final_cltv_expiry_delta`. - pub fn min_final_cltv_expiry_delta(mut self, min_final_cltv_expiry_delta: u64) -> InvoiceBuilder { + pub fn min_final_cltv_expiry_delta(mut self, min_final_cltv_expiry_delta: u64) -> InvoiceBuilder { self.tagged_fields.push(TaggedField::MinFinalCltvExpiryDelta(MinFinalCltvExpiryDelta(min_final_cltv_expiry_delta))); self.set_flags() } } -impl InvoiceBuilder { +impl InvoiceBuilder { /// Sets the payment secret and relevant features. - pub fn payment_secret(mut self, payment_secret: PaymentSecret) -> InvoiceBuilder { - let mut features = InvoiceFeatures::empty(); - features.set_variable_length_onion_required(); - features.set_payment_secret_required(); + pub fn payment_secret(mut self, payment_secret: PaymentSecret) -> InvoiceBuilder { + let mut found_features = false; + for field in self.tagged_fields.iter_mut() { + if let TaggedField::Features(f) = field { + found_features = true; + f.set_variable_length_onion_required(); + f.set_payment_secret_required(); + } + } self.tagged_fields.push(TaggedField::PaymentSecret(payment_secret)); - self.tagged_fields.push(TaggedField::Features(features)); + if !found_features { + let mut features = InvoiceFeatures::empty(); + features.set_variable_length_onion_required(); + features.set_payment_secret_required(); + self.tagged_fields.push(TaggedField::Features(features)); + } self.set_flags() } } -impl InvoiceBuilder { +impl InvoiceBuilder { + /// Sets the payment metadata. + /// + /// By default features are set to *optionally* allow the sender to include the payment metadata. + /// If you wish to require that the sender include the metadata (and fail to parse the invoice if + /// they don't support payment metadata fields), you need to call + /// [`InvoiceBuilder::require_payment_metadata`] after this. + pub fn payment_metadata(mut self, payment_metadata: Vec) -> InvoiceBuilder { + self.tagged_fields.push(TaggedField::PaymentMetadata(payment_metadata)); + let mut found_features = false; + for field in self.tagged_fields.iter_mut() { + if let TaggedField::Features(f) = field { + found_features = true; + f.set_payment_metadata_optional(); + } + } + if !found_features { + let mut features = InvoiceFeatures::empty(); + features.set_payment_metadata_optional(); + self.tagged_fields.push(TaggedField::Features(features)); + } + self.set_flags() + } +} + +impl InvoiceBuilder { + /// Sets forwarding of payment metadata as required. A reader of the invoice which does not + /// support sending payment metadata will fail to read the invoice. + pub fn require_payment_metadata(mut self) -> InvoiceBuilder { + for field in self.tagged_fields.iter_mut() { + if let TaggedField::Features(f) = field { + f.set_payment_metadata_required(); + } + } + self + } +} + +impl InvoiceBuilder { /// Sets the `basic_mpp` feature as optional. pub fn basic_mpp(mut self) -> Self { for field in self.tagged_fields.iter_mut() { @@ -704,7 +758,7 @@ impl InvoiceBuilder { +impl InvoiceBuilder { /// Builds and signs an invoice using the supplied `sign_function`. This function MAY NOT fail /// and MUST produce a recoverable signature valid for the given hash and if applicable also for /// the included payee public key. diff --git a/lightning-invoice/tests/ser_de.rs b/lightning-invoice/tests/ser_de.rs index 73e2ea838..ef5a4d32b 100644 --- a/lightning-invoice/tests/ser_de.rs +++ b/lightning-invoice/tests/ser_de.rs @@ -332,6 +332,56 @@ fn get_test_tuples() -> Vec<(String, SignedRawInvoice, bool, bool)> { true, // Different features than set in InvoiceBuilder true, // Some unknown fields ), + ( // Older version of the payment metadata test with a payment_pubkey set + "lnbc10m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdp9wpshjmt9de6zqmt9w3skgct5vysxjmnnd9jx2mq8q8a04uqnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66sp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q2gqqqqqqsgqy9gw6ymamd20jumvdgpfphkhp8fzhhdhycw36egcmla5vlrtrmhs9t7psfy3hkkdqzm9eq64fjg558znccds5nhsfmxveha5xe0dykgpspdha0".to_owned(), + InvoiceBuilder::new(Currency::Bitcoin) + .amount_milli_satoshis(1_000_000_000) + .duration_since_epoch(Duration::from_secs(1496314658)) + .payment_hash(sha256::Hash::from_hex( + "0001020304050607080900010203040506070809000102030405060708090102" + ).unwrap()) + .description("payment metadata inside".to_owned()) + .payment_metadata(hex::decode("01fafaf0").unwrap()) + .require_payment_metadata() + .payee_pub_key(PublicKey::from_slice(&hex::decode( + "03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad" + ).unwrap()).unwrap()) + .payment_secret(PaymentSecret([0x11; 32])) + .build_raw() + .unwrap() + .sign(|_| { + RecoverableSignature::from_compact( + &hex::decode("2150ed137ddb54f9736c6a0290ded709d22bddb7261d1d6518dffb467c6b1eef02afc182491bdacd00b65c83554c914a1c53c61b0a4ef04eccccdfb4365ed259").unwrap(), + RecoveryId::from_i32(1).unwrap() + ) + }).unwrap(), + false, // Different features than set in InvoiceBuilder + true, // Some unknown fields + ), + ( + "lnbc10m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdp9wpshjmt9de6zqmt9w3skgct5vysxjmnnd9jx2mq8q8a04uqsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q2gqqqqqqsgq7hf8he7ecf7n4ffphs6awl9t6676rrclv9ckg3d3ncn7fct63p6s365duk5wrk202cfy3aj5xnnp5gs3vrdvruverwwq7yzhkf5a3xqpd05wjc".to_owned(), + InvoiceBuilder::new(Currency::Bitcoin) + .amount_milli_satoshis(1_000_000_000) + .duration_since_epoch(Duration::from_secs(1496314658)) + .payment_hash(sha256::Hash::from_hex( + "0001020304050607080900010203040506070809000102030405060708090102" + ).unwrap()) + .description("payment metadata inside".to_owned()) + .payment_metadata(hex::decode("01fafaf0").unwrap()) + .require_payment_metadata() + .payment_secret(PaymentSecret([0x11; 32])) + .build_raw() + .unwrap() + .sign(|_| { + RecoverableSignature::from_compact( + &hex::decode("f5d27be7d9c27d3aa521bc35d77cabd6bda18f1f61716445b19e27e4e17a887508ea8de5a8e1d94f561248f65434e61a221160dac1f1991b9c0f1057b269d898").unwrap(), + RecoveryId::from_i32(1).unwrap() + ) + }).unwrap(), + false, // Different features than set in InvoiceBuilder + true, // Some unknown fields + ), + ] }