Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle Payment Metadata in the Invoice/Send/Receive Pipelines #1221

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions fuzz/src/chanmon_consistency.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ fn send_payment(source: &ChanMan, dest: &ChanMan, dest_chan_id: u64, amt: u64, p
cltv_expiry_delta: 200,
}]],
payment_params: None,
}, payment_hash, &Some(payment_secret)) {
}, payment_hash, &Some(payment_secret), None) {
check_payment_err(err);
false
} else { true }
Expand All @@ -336,7 +336,7 @@ fn send_hop_payment(source: &ChanMan, middle: &ChanMan, middle_chan_id: u64, des
cltv_expiry_delta: 200,
}]],
payment_params: None,
}, payment_hash, &Some(payment_secret)) {
}, payment_hash, &Some(payment_secret), None) {
check_payment_err(err);
false
} else { true }
Expand Down
4 changes: 2 additions & 2 deletions fuzz/src/full_stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,7 @@ pub fn do_test(data: &[u8], logger: &Arc<dyn Logger>) {
sha.input(&payment_hash.0[..]);
payment_hash.0 = Sha256::from_engine(sha).into_inner();
payments_sent += 1;
match channelmanager.send_payment(&route, payment_hash, &None) {
match channelmanager.send_payment(&route, payment_hash, &None, None) {
Ok(_) => {},
Err(_) => return,
}
Expand All @@ -490,7 +490,7 @@ pub fn do_test(data: &[u8], logger: &Arc<dyn Logger>) {
let mut payment_secret = PaymentSecret([0; 32]);
payment_secret.0[0..8].copy_from_slice(&be64_to_array(payments_sent));
payments_sent += 1;
match channelmanager.send_payment(&route, payment_hash, &Some(payment_secret)) {
match channelmanager.send_payment(&route, payment_hash, &Some(payment_secret), None) {
Ok(_) => {},
Err(_) => return,
}
Expand Down
2 changes: 2 additions & 0 deletions lightning-invoice/src/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,8 @@ impl FromBase32 for TaggedField {
Ok(TaggedField::PrivateRoute(PrivateRoute::from_base32(field_data)?)),
constants::TAG_PAYMENT_SECRET =>
Ok(TaggedField::PaymentSecret(PaymentSecret::from_base32(field_data)?)),
constants::TAG_PAYMENT_METADATA =>
Ok(TaggedField::PaymentMetadata(Vec::<u8>::from_base32(field_data)?)),
constants::TAG_FEATURES =>
Ok(TaggedField::Features(InvoiceFeatures::from_base32(field_data)?)),
_ => {
Expand Down
131 changes: 105 additions & 26 deletions lightning-invoice/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,10 +163,13 @@ pub const DEFAULT_MIN_FINAL_CLTV_EXPIRY: u64 = 18;
/// * `D`: exactly one `Description` or `DescriptionHash`
/// * `H`: exactly one `PaymentHash`
/// * `T`: the timestamp is set
/// * `C`: the CLTV expiry is set
/// * `S`: the payment secret is set
/// * `M`: payment metadata 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<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> {
pub struct InvoiceBuilder<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> {
currency: Currency,
amount: Option<u64>,
si_prefix: Option<SiPrefix>,
Expand All @@ -179,6 +182,7 @@ pub struct InvoiceBuilder<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S:
phantom_t: core::marker::PhantomData<T>,
phantom_c: core::marker::PhantomData<C>,
phantom_s: core::marker::PhantomData<S>,
phantom_m: core::marker::PhantomData<M>,
}

/// Represents a syntactically and semantically correct lightning BOLT11 invoice.
Expand Down Expand Up @@ -362,6 +366,7 @@ pub enum TaggedField {
Fallback(Fallback),
PrivateRoute(PrivateRoute),
PaymentSecret(PaymentSecret),
PaymentMetadata(Vec<u8>),
Features(InvoiceFeatures),
}

Expand Down Expand Up @@ -427,10 +432,11 @@ pub mod constants {
pub const TAG_FALLBACK: u8 = 9;
pub const TAG_PRIVATE_ROUTE: u8 = 3;
pub const TAG_PAYMENT_SECRET: u8 = 16;
pub const TAG_PAYMENT_METADATA: u8 = 27;
pub const TAG_FEATURES: u8 = 5;
}

impl InvoiceBuilder<tb::False, tb::False, tb::False, tb::False, tb::False> {
impl InvoiceBuilder<tb::False, tb::False, tb::False, tb::False, tb::False, tb::False> {
/// Construct new, empty `InvoiceBuilder`. All necessary fields have to be filled first before
/// `InvoiceBuilder::build(self)` becomes available.
pub fn new(currrency: Currency) -> Self {
Expand All @@ -447,14 +453,15 @@ impl InvoiceBuilder<tb::False, tb::False, tb::False, tb::False, tb::False> {
phantom_t: core::marker::PhantomData,
phantom_c: core::marker::PhantomData,
phantom_s: core::marker::PhantomData,
phantom_m: core::marker::PhantomData,
}
}
}

impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, T, C, S> {
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, T, C, S, M> {
/// Helper function to set the completeness flags.
fn set_flags<DN: tb::Bool, HN: tb::Bool, TN: tb::Bool, CN: tb::Bool, SN: tb::Bool>(self) -> InvoiceBuilder<DN, HN, TN, CN, SN> {
InvoiceBuilder::<DN, HN, TN, CN, SN> {
fn set_flags<DN: tb::Bool, HN: tb::Bool, TN: tb::Bool, CN: tb::Bool, SN: tb::Bool, MN: tb::Bool>(self) -> InvoiceBuilder<DN, HN, TN, CN, SN, MN> {
InvoiceBuilder::<DN, HN, TN, CN, SN, MN> {
currency: self.currency,
amount: self.amount,
si_prefix: self.si_prefix,
Expand All @@ -467,6 +474,7 @@ impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBui
phantom_t: core::marker::PhantomData,
phantom_c: core::marker::PhantomData,
phantom_s: core::marker::PhantomData,
phantom_m: core::marker::PhantomData,
}
}

Expand Down Expand Up @@ -510,7 +518,7 @@ impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBui
}
}

impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, tb::True, C, S> {
impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, tb::True, C, S, M> {
/// Builds a `RawInvoice` if no `CreationError` occurred while construction any of the fields.
pub fn build_raw(self) -> Result<RawInvoice, CreationError> {

Expand Down Expand Up @@ -543,9 +551,9 @@ impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, tb
}
}

impl<H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<tb::False, H, T, C, S> {
impl<H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<tb::False, H, T, C, S, M> {
/// Set the description. This function is only available if no description (hash) was set.
pub fn description(mut self, description: String) -> InvoiceBuilder<tb::True, H, T, C, S> {
pub fn description(mut self, description: String) -> InvoiceBuilder<tb::True, H, T, C, S, M> {
match Description::new(description) {
Ok(d) => self.tagged_fields.push(TaggedField::Description(d)),
Err(e) => self.error = Some(e),
Expand All @@ -554,24 +562,24 @@ impl<H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<tb::Fals
}

/// 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<tb::True, H, T, C, S> {
pub fn description_hash(mut self, description_hash: sha256::Hash) -> InvoiceBuilder<tb::True, H, T, C, S, M> {
self.tagged_fields.push(TaggedField::DescriptionHash(Sha256(description_hash)));
self.set_flags()
}
}

impl<D: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, tb::False, T, C, S> {
impl<D: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<D, tb::False, T, C, S, M> {
/// 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<D, tb::True, T, C, S> {
pub fn payment_hash(mut self, hash: sha256::Hash) -> InvoiceBuilder<D, tb::True, T, C, S, M> {
self.tagged_fields.push(TaggedField::PaymentHash(Sha256(hash)));
self.set_flags()
}
}

impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, tb::False, C, S> {
impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, tb::False, C, S, M> {
/// Sets the timestamp to a specific [`SystemTime`].
#[cfg(feature = "std")]
pub fn timestamp(mut self, time: SystemTime) -> InvoiceBuilder<D, H, tb::True, C, S> {
pub fn timestamp(mut self, time: SystemTime) -> InvoiceBuilder<D, H, tb::True, C, S, M> {
match PositiveTimestamp::from_system_time(time) {
Ok(t) => self.timestamp = Some(t),
Err(e) => self.error = Some(e),
Expand All @@ -581,7 +589,7 @@ impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, tb
}

/// Sets the timestamp to a duration since the Unix epoch.
pub fn duration_since_epoch(mut self, time: Duration) -> InvoiceBuilder<D, H, tb::True, C, S> {
pub fn duration_since_epoch(mut self, time: Duration) -> InvoiceBuilder<D, H, tb::True, C, S, M> {
match PositiveTimestamp::from_duration_since_epoch(time) {
Ok(t) => self.timestamp = Some(t),
Err(e) => self.error = Some(e),
Expand All @@ -592,34 +600,94 @@ impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, tb

/// Sets the timestamp to the current system time.
#[cfg(feature = "std")]
pub fn current_timestamp(mut self) -> InvoiceBuilder<D, H, tb::True, C, S> {
pub fn current_timestamp(mut self) -> InvoiceBuilder<D, H, tb::True, C, S. M> {
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<D: tb::Bool, H: tb::Bool, T: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, T, tb::False, S> {
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, S: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, T, tb::False, S, M> {
/// Sets `min_final_cltv_expiry`.
pub fn min_final_cltv_expiry(mut self, min_final_cltv_expiry: u64) -> InvoiceBuilder<D, H, T, tb::True, S> {
pub fn min_final_cltv_expiry(mut self, min_final_cltv_expiry: u64) -> InvoiceBuilder<D, H, T, tb::True, S, M> {
self.tagged_fields.push(TaggedField::MinFinalCltvExpiry(MinFinalCltvExpiry(min_final_cltv_expiry)));
self.set_flags()
}
}

impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool> InvoiceBuilder<D, H, T, C, tb::False> {
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, T, C, tb::False, M> {
/// Sets the payment secret and relevant features.
pub fn payment_secret(mut self, payment_secret: PaymentSecret) -> InvoiceBuilder<D, H, T, C, tb::True> {
let features = InvoiceFeatures::empty()
.set_variable_length_onion_required()
.set_payment_secret_required();
pub fn payment_secret(mut self, payment_secret: PaymentSecret) -> InvoiceBuilder<D, H, T, C, tb::True, M> {
let mut found_features = false;
self.tagged_fields = self.tagged_fields
.drain(..)
.map(|field| match field {
TaggedField::Features(f) => {
found_features = true;
TaggedField::Features(f
.set_variable_length_onion_required()
.set_payment_secret_required())
},
_ => field,
})
.collect();
self.tagged_fields.push(TaggedField::PaymentSecret(payment_secret));
self.tagged_fields.push(TaggedField::Features(features));
if !found_features {
let features = InvoiceFeatures::empty()
.set_variable_length_onion_required()
.set_payment_secret_required();
self.tagged_fields.push(TaggedField::Features(features));
}
self.set_flags()
}
}

impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool> InvoiceBuilder<D, H, T, C, tb::True> {
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, T, C, S, tb::False> {
/// 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<u8>) -> InvoiceBuilder<D, H, T, C, S, tb::True> {
self.tagged_fields.push(TaggedField::PaymentMetadata(payment_metadata));
let mut found_features = false;
self.tagged_fields = self.tagged_fields
.drain(..)
.map(|field| match field {
TaggedField::Features(f) => {
found_features = true;
TaggedField::Features(f.set_payment_metadata_optional())
},
_ => field,
})
.collect();
if !found_features {
let features = InvoiceFeatures::empty()
.set_payment_metadata_optional();
self.tagged_fields.push(TaggedField::Features(features));
}
self.set_flags()
}
}

impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, T, C, S, tb::True> {
/// Sets the payment secret and relevant features.
pub fn require_payment_metadata(mut self) -> InvoiceBuilder<D, H, T, C, S, tb::True> {
self.tagged_fields = self.tagged_fields
.drain(..)
.map(|field| match field {
TaggedField::Features(f) => {
TaggedField::Features(f.set_payment_metadata_required())
},
_ => field,
})
.collect();
self
}
}

impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, M: tb::Bool> InvoiceBuilder<D, H, T, C, tb::True, M> {
/// Sets the `basic_mpp` feature as optional.
pub fn basic_mpp(mut self) -> Self {
self.tagged_fields = self.tagged_fields
Expand All @@ -633,7 +701,7 @@ impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool> InvoiceBuilder<D, H, T,
}
}

impl InvoiceBuilder<tb::True, tb::True, tb::True, tb::True, tb::True> {
impl<M: tb::Bool> InvoiceBuilder<tb::True, tb::True, tb::True, tb::True, tb::True, M> {
/// 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.
Expand Down Expand Up @@ -886,6 +954,10 @@ impl RawInvoice {
find_extract!(self.known_tagged_fields(), TaggedField::PaymentSecret(ref x), x)
}

pub fn payment_metadata(&self) -> Option<&Vec<u8>> {
find_extract!(self.known_tagged_fields(), TaggedField::PaymentMetadata(ref x), x)
}

pub fn features(&self) -> Option<&InvoiceFeatures> {
find_extract!(self.known_tagged_fields(), TaggedField::Features(ref x), x)
}
Expand Down Expand Up @@ -1148,6 +1220,11 @@ impl Invoice {
self.signed_invoice.payment_secret().expect("was checked by constructor")
}

/// Get the payment metadata blob if one was included in the invoice
pub fn payment_metadata(&self) -> Option<&Vec<u8>> {
self.signed_invoice.payment_metadata()
}

/// Get the invoice features if they were included in the invoice
pub fn features(&self) -> Option<&InvoiceFeatures> {
self.signed_invoice.features()
Expand Down Expand Up @@ -1250,6 +1327,7 @@ impl TaggedField {
TaggedField::Fallback(_) => constants::TAG_FALLBACK,
TaggedField::PrivateRoute(_) => constants::TAG_PRIVATE_ROUTE,
TaggedField::PaymentSecret(_) => constants::TAG_PAYMENT_SECRET,
TaggedField::PaymentMetadata(_) => constants::TAG_PAYMENT_METADATA,
TaggedField::Features(_) => constants::TAG_FEATURES,
};

Expand Down Expand Up @@ -1875,7 +1953,8 @@ mod test {
);
assert_eq!(invoice.payment_hash(), &sha256::Hash::from_slice(&[21;32][..]).unwrap());
assert_eq!(invoice.payment_secret(), &PaymentSecret([42; 32]));
assert_eq!(invoice.features(), Some(&InvoiceFeatures::known()));
assert_eq!(invoice.features(), Some(&InvoiceFeatures::empty()
.set_payment_secret_required().set_variable_length_onion_required().set_basic_mpp_optional()));

let raw_invoice = builder.build_raw().unwrap();
assert_eq!(raw_invoice, *invoice.into_signed_raw().raw_invoice())
Expand Down
15 changes: 7 additions & 8 deletions lightning-invoice/src/payment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@
//! # impl Payer for FakePayer {
//! # fn node_id(&self) -> PublicKey { unimplemented!() }
//! # fn first_hops(&self) -> Vec<ChannelDetails> { unimplemented!() }
//! # fn send_payment(
//! # &self, route: &Route, payment_hash: PaymentHash, payment_secret: &Option<PaymentSecret>
//! # fn send_payment(&self, route: &Route, payment_hash: PaymentHash,
//! # payment_secret: &Option<PaymentSecret>, payment_metadata: Option<Vec<u8>>
//! # ) -> Result<PaymentId, PaymentSendFailure> { unimplemented!() }
//! # fn send_spontaneous_payment(
//! # &self, route: &Route, payment_preimage: PaymentPreimage
Expand Down Expand Up @@ -186,8 +186,8 @@ pub trait Payer {
fn first_hops(&self) -> Vec<ChannelDetails>;

/// Sends a payment over the Lightning Network using the given [`Route`].
fn send_payment(
&self, route: &Route, payment_hash: PaymentHash, payment_secret: &Option<PaymentSecret>
fn send_payment(&self, route: &Route, payment_hash: PaymentHash,
payment_secret: &Option<PaymentSecret>, payment_metadata: Option<Vec<u8>>
) -> Result<PaymentId, PaymentSendFailure>;

/// Sends a spontaneous payment over the Lightning Network using the given [`Route`].
Expand Down Expand Up @@ -309,7 +309,7 @@ where
};

let send_payment = |route: &Route| {
self.payer.send_payment(route, payment_hash, &payment_secret)
self.payer.send_payment(route, payment_hash, &payment_secret, invoice.payment_metadata().cloned())
};
self.pay_internal(&route_params, payment_hash, send_payment)
.map_err(|e| { self.payment_cache.lock().unwrap().remove(&payment_hash); e })
Expand Down Expand Up @@ -1463,9 +1463,8 @@ mod tests {
Vec::new()
}

fn send_payment(
&self, route: &Route, _payment_hash: PaymentHash,
_payment_secret: &Option<PaymentSecret>
fn send_payment(&self, route: &Route, _payment_hash: PaymentHash,
_payment_secret: &Option<PaymentSecret>, _payment_metadata: Option<Vec<u8>>
) -> Result<PaymentId, PaymentSendFailure> {
self.check_value_msats(Amount::ForInvoice(route.get_total_amount()));
self.check_attempts()
Expand Down
3 changes: 3 additions & 0 deletions lightning-invoice/src/ser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,9 @@ impl ToBase32 for TaggedField {
TaggedField::PaymentSecret(ref payment_secret) => {
write_tagged_field(writer, constants::TAG_PAYMENT_SECRET, payment_secret)
},
TaggedField::PaymentMetadata(ref payment_metadata) => {
write_tagged_field(writer, constants::TAG_PAYMENT_METADATA, payment_metadata)
},
TaggedField::Features(ref features) => {
write_tagged_field(writer, constants::TAG_FEATURES, features)
},
Expand Down
Loading