X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Foffers%2Foffer.rs;h=887318c0c0ea8dccffad71d61ecfdaaf045da6df;hb=bd962fc2eb00f77da41c3c5de24d5a5337a2150d;hp=6a8f956ae635eb8f3278ee85f48893a9fb8f5aaf;hpb=dd2ccd232234d93c482c333b20dc71d53c4b7247;p=rust-lightning diff --git a/lightning/src/offers/offer.rs b/lightning/src/offers/offer.rs index 6a8f956a..887318c0 100644 --- a/lightning/src/offers/offer.rs +++ b/lightning/src/offers/offer.rs @@ -27,7 +27,7 @@ //! use lightning::offers::parse::ParseError; //! use lightning::util::ser::{Readable, Writeable}; //! -//! # use lightning::onion_message::BlindedPath; +//! # use lightning::blinded_path::BlindedPath; //! # #[cfg(feature = "std")] //! # use std::time::SystemTime; //! # @@ -68,7 +68,7 @@ use bitcoin::blockdata::constants::ChainHash; use bitcoin::network::constants::Network; -use bitcoin::secp256k1::{PublicKey, Secp256k1, self}; +use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, self}; use core::convert::TryFrom; use core::num::NonZeroU64; use core::ops::Deref; @@ -76,14 +76,14 @@ use core::str::FromStr; use core::time::Duration; use crate::chain::keysinterface::EntropySource; use crate::io; +use crate::blinded_path::BlindedPath; use crate::ln::features::OfferFeatures; use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce}; use crate::ln::msgs::MAX_VALUE_MSAT; -use crate::offers::invoice_request::InvoiceRequestBuilder; +use crate::offers::invoice_request::{DerivedPayerId, ExplicitPayerId, InvoiceRequestBuilder}; use crate::offers::merkle::TlvStream; use crate::offers::parse::{Bech32Encode, ParseError, ParsedMessage, SemanticError}; use crate::offers::signer::{Metadata, MetadataMaterial, self}; -use crate::onion_message::BlindedPath; use crate::util::ser::{HighZeroBytesDroppedBigSize, WithoutLength, Writeable, Writer}; use crate::util::string::PrintableString; @@ -92,7 +92,7 @@ use crate::prelude::*; #[cfg(feature = "std")] use std::time::SystemTime; -const IV_BYTES: &[u8; IV_LEN] = b"LDK Offer ~~~~~~"; +pub(super) const IV_BYTES: &[u8; IV_LEN] = b"LDK Offer ~~~~~~"; /// Builds an [`Offer`] for the "offer to be paid" flow. /// @@ -384,7 +384,7 @@ impl Offer { /// A complete description of the purpose of the payment. Intended to be displayed to the user /// but with the caveat that it has not been verified in any way. pub fn description(&self) -> PrintableString { - PrintableString(&self.contents.description) + self.contents.description() } /// Features pertaining to the offer. @@ -439,8 +439,53 @@ impl Offer { self.contents.signing_pubkey() } - /// Creates an [`InvoiceRequest`] for the offer with the given `metadata` and `payer_id`, which - /// will be reflected in the `Invoice` response. + /// Similar to [`Offer::request_invoice`] except it: + /// - derives the [`InvoiceRequest::payer_id`] such that a different key can be used for each + /// request, and + /// - sets the [`InvoiceRequest::metadata`] when [`InvoiceRequestBuilder::build`] is called such + /// that it can be used by [`Invoice::verify`] to determine if the invoice was requested using + /// a base [`ExpandedKey`] from which the payer id was derived. + /// + /// Useful to protect the sender's privacy. + /// + /// [`InvoiceRequest::payer_id`]: crate::offers::invoice_request::InvoiceRequest::payer_id + /// [`InvoiceRequest::metadata`]: crate::offers::invoice_request::InvoiceRequest::metadata + /// [`Invoice::verify`]: crate::offers::invoice::Invoice::verify + /// [`ExpandedKey`]: crate::ln::inbound_payment::ExpandedKey + pub fn request_invoice_deriving_payer_id<'a, 'b, ES: Deref, T: secp256k1::Signing>( + &'a self, expanded_key: &ExpandedKey, entropy_source: ES, secp_ctx: &'b Secp256k1 + ) -> Result, SemanticError> + where + ES::Target: EntropySource, + { + if self.features().requires_unknown_bits() { + return Err(SemanticError::UnknownRequiredFeatures); + } + + Ok(InvoiceRequestBuilder::deriving_payer_id(self, expanded_key, entropy_source, secp_ctx)) + } + + /// Similar to [`Offer::request_invoice_deriving_payer_id`] except uses `payer_id` for the + /// [`InvoiceRequest::payer_id`] instead of deriving a different key for each request. + /// + /// Useful for recurring payments using the same `payer_id` with different invoices. + /// + /// [`InvoiceRequest::payer_id`]: crate::offers::invoice_request::InvoiceRequest::payer_id + pub fn request_invoice_deriving_metadata( + &self, payer_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES + ) -> Result, SemanticError> + where + ES::Target: EntropySource, + { + if self.features().requires_unknown_bits() { + return Err(SemanticError::UnknownRequiredFeatures); + } + + Ok(InvoiceRequestBuilder::deriving_metadata(self, payer_id, expanded_key, entropy_source)) + } + + /// Creates an [`InvoiceRequestBuilder`] for the offer with the given `metadata` and `payer_id`, + /// which will be reflected in the `Invoice` response. /// /// The `metadata` is useful for including information about the derivation of `payer_id` such /// that invoice response handling can be stateless. Also serves as payer-provided entropy while @@ -454,7 +499,7 @@ impl Offer { /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest pub fn request_invoice( &self, metadata: Vec, payer_id: PublicKey - ) -> Result { + ) -> Result, SemanticError> { if self.features().requires_unknown_bits() { return Err(SemanticError::UnknownRequiredFeatures); } @@ -491,6 +536,10 @@ impl OfferContents { self.metadata.as_ref().and_then(|metadata| metadata.as_bytes()) } + pub fn description(&self) -> PrintableString { + PrintableString(&self.description) + } + #[cfg(feature = "std")] pub(super) fn is_expired(&self) -> bool { match self.absolute_expiry { @@ -570,11 +619,11 @@ impl OfferContents { /// Verifies that the offer metadata was produced from the offer in the TLV stream. pub(super) fn verify( - &self, tlv_stream: TlvStream<'_>, key: &ExpandedKey, secp_ctx: &Secp256k1 - ) -> bool { + &self, bytes: &[u8], key: &ExpandedKey, secp_ctx: &Secp256k1 + ) -> Result, ()> { match self.metadata() { Some(metadata) => { - let tlv_stream = tlv_stream.range(OFFER_TYPES).filter(|record| { + let tlv_stream = TlvStream::new(bytes).range(OFFER_TYPES).filter(|record| { match record.r#type { OFFER_METADATA_TYPE => false, OFFER_NODE_ID_TYPE => !self.metadata.as_ref().unwrap().derives_keys(), @@ -585,7 +634,7 @@ impl OfferContents { metadata, key, IV_BYTES, self.signing_pubkey(), tlv_stream, secp_ctx ) }, - None => false, + None => Err(()), } } @@ -677,7 +726,7 @@ impl Quantity { } /// Valid type range for offer TLV records. -const OFFER_TYPES: core::ops::Range = 1..80; +pub(super) const OFFER_TYPES: core::ops::Range = 1..80; /// TLV record type for [`Offer::metadata`]. const OFFER_METADATA_TYPE: u64 = 4; @@ -787,13 +836,13 @@ mod tests { use core::convert::TryFrom; use core::num::NonZeroU64; use core::time::Duration; + use crate::blinded_path::{BlindedHop, BlindedPath}; use crate::chain::keysinterface::KeyMaterial; use crate::ln::features::OfferFeatures; use crate::ln::inbound_payment::ExpandedKey; use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT}; use crate::offers::parse::{ParseError, SemanticError}; use crate::offers::test_utils::*; - use crate::onion_message::{BlindedHop, BlindedPath}; use crate::util::ser::{BigSize, Writeable}; use crate::util::string::PrintableString; @@ -917,7 +966,7 @@ mod tests { let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap() .build().unwrap() .sign(payer_sign).unwrap(); - assert!(invoice_request.verify(&expanded_key, &secp_ctx)); + assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_ok()); // Fails verification with altered offer field let mut tlv_stream = offer.as_tlv_stream(); @@ -930,7 +979,7 @@ mod tests { .request_invoice(vec![1; 32], payer_pubkey()).unwrap() .build().unwrap() .sign(payer_sign).unwrap(); - assert!(!invoice_request.verify(&expanded_key, &secp_ctx)); + assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_err()); // Fails verification with altered metadata let mut tlv_stream = offer.as_tlv_stream(); @@ -944,7 +993,7 @@ mod tests { .request_invoice(vec![1; 32], payer_pubkey()).unwrap() .build().unwrap() .sign(payer_sign).unwrap(); - assert!(!invoice_request.verify(&expanded_key, &secp_ctx)); + assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_err()); } #[test] @@ -974,7 +1023,7 @@ mod tests { let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap() .build().unwrap() .sign(payer_sign).unwrap(); - assert!(invoice_request.verify(&expanded_key, &secp_ctx)); + assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_ok()); // Fails verification with altered offer field let mut tlv_stream = offer.as_tlv_stream(); @@ -987,7 +1036,7 @@ mod tests { .request_invoice(vec![1; 32], payer_pubkey()).unwrap() .build().unwrap() .sign(payer_sign).unwrap(); - assert!(!invoice_request.verify(&expanded_key, &secp_ctx)); + assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_err()); // Fails verification with altered signing pubkey let mut tlv_stream = offer.as_tlv_stream(); @@ -1001,7 +1050,7 @@ mod tests { .request_invoice(vec![1; 32], payer_pubkey()).unwrap() .build().unwrap() .sign(payer_sign).unwrap(); - assert!(!invoice_request.verify(&expanded_key, &secp_ctx)); + assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_err()); } #[test]