From 4624caf5a20b0329160e872259911a57eb451634 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Fri, 9 Aug 2024 00:13:41 +0000 Subject: [PATCH 01/11] Move `Payment{Hash,Preimage,Secret}` into a new crate `lightning-invoice` currently has a dependency on the entire `lightning` crate just because it wants to use some of the useful types from it. This is obviously backwards and leads to some awkwardness like the BOLT 11 invoice signing API in the `lightning` crate taking a `[u5]` rather than a `Bolt11Invoice`. This is the first step towards fixing that - moving the common types we need into a new `lightning-types` crate which both can depend on. Since we're using a new crate and can't depend on the existing `lightning` hex utility to implement `Display`, we also take this opportunity to switch to the new `Display` impl macro in `hex_conservative`. --- Cargo.toml | 1 + fuzz/src/chanmon_consistency.rs | 2 +- fuzz/src/full_stack.rs | 2 +- fuzz/src/router.rs | 2 +- lightning-types/Cargo.toml | 24 +++++++ lightning-types/src/lib.rs | 26 ++++++++ lightning-types/src/payment.rs | 115 ++++++++++++++++++++++++++++++++ lightning/Cargo.toml | 2 + lightning/src/lib.rs | 3 + lightning/src/ln/mod.rs | 2 +- lightning/src/ln/types.rs | 70 +------------------ 11 files changed, 176 insertions(+), 73 deletions(-) create mode 100644 lightning-types/Cargo.toml create mode 100644 lightning-types/src/lib.rs create mode 100644 lightning-types/src/payment.rs diff --git a/Cargo.toml b/Cargo.toml index 0aa7f7624d8..f7f4ba021fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ resolver = "2" members = [ "lightning", + "lightning-types", "lightning-block-sync", "lightning-invoice", "lightning-net-tokio", diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index 7e95dc07f33..06d2f6c845f 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -56,7 +56,7 @@ use lightning::ln::msgs::{ self, ChannelMessageHandler, CommitmentUpdate, DecodeError, Init, UpdateAddHTLC, }; use lightning::ln::script::ShutdownScript; -use lightning::ln::{ChannelId, PaymentHash, PaymentPreimage, PaymentSecret}; +use lightning::ln::types::{ChannelId, PaymentHash, PaymentPreimage, PaymentSecret}; use lightning::offers::invoice::{BlindedPayInfo, UnsignedBolt12Invoice}; use lightning::offers::invoice_request::UnsignedInvoiceRequest; use lightning::onion_message::messenger::{Destination, MessageRouter, OnionMessagePath}; diff --git a/fuzz/src/full_stack.rs b/fuzz/src/full_stack.rs index 5499dc5e902..55a96521700 100644 --- a/fuzz/src/full_stack.rs +++ b/fuzz/src/full_stack.rs @@ -49,7 +49,7 @@ use lightning::ln::peer_handler::{ IgnoringMessageHandler, MessageHandler, PeerManager, SocketDescriptor, }; use lightning::ln::script::ShutdownScript; -use lightning::ln::{ChannelId, PaymentHash, PaymentPreimage, PaymentSecret}; +use lightning::ln::types::{ChannelId, PaymentHash, PaymentPreimage, PaymentSecret}; use lightning::offers::invoice::{BlindedPayInfo, UnsignedBolt12Invoice}; use lightning::offers::invoice_request::UnsignedInvoiceRequest; use lightning::onion_message::messenger::{Destination, MessageRouter, OnionMessagePath}; diff --git a/fuzz/src/router.rs b/fuzz/src/router.rs index 034672fb275..96046f25842 100644 --- a/fuzz/src/router.rs +++ b/fuzz/src/router.rs @@ -18,7 +18,7 @@ use lightning::ln::channel_state::{ChannelCounterparty, ChannelDetails, ChannelS use lightning::ln::channelmanager; use lightning::ln::features::{BlindedHopFeatures, Bolt12InvoiceFeatures}; use lightning::ln::msgs; -use lightning::ln::ChannelId; +use lightning::ln::types::ChannelId; use lightning::offers::invoice::BlindedPayInfo; use lightning::routing::gossip::{NetworkGraph, RoutingFees}; use lightning::routing::router::{ diff --git a/lightning-types/Cargo.toml b/lightning-types/Cargo.toml new file mode 100644 index 00000000000..6bdd65d42a9 --- /dev/null +++ b/lightning-types/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "lightning-types" +version = "0.1.0" +authors = ["Matt Corallo"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/lightningdevkit/rust-lightning/" +description = """ +Basic types which are used in the lightning network +""" +edition = "2021" + +[package.metadata.docs.rs] +rustdoc-args = ["--cfg", "docsrs"] + +[features] + +[dependencies] +bitcoin = { version = "0.31", default-features = false } +# TODO: Once we switch to bitcoin 0.32 drop this explicit dep: +hex-conservative = { version = "0.2", default-features = false } +bech32 = { version = "0.9", default-features = false } + +[lints] +workspace = true diff --git a/lightning-types/src/lib.rs b/lightning-types/src/lib.rs new file mode 100644 index 00000000000..0174bcc9b3d --- /dev/null +++ b/lightning-types/src/lib.rs @@ -0,0 +1,26 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +#![crate_name = "lightning_types"] + +//! Various types which are used in the lightning network. +//! +//! See the `lightning` crate for usage of these. + +#![cfg_attr(not(test), no_std)] +#![deny(missing_docs)] +#![forbid(unsafe_code)] +#![deny(rustdoc::broken_intra_doc_links)] +#![deny(rustdoc::private_intra_doc_links)] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] + +extern crate alloc; +extern crate core; + +pub mod payment; diff --git a/lightning-types/src/payment.rs b/lightning-types/src/payment.rs new file mode 100644 index 00000000000..1c36f332d1b --- /dev/null +++ b/lightning-types/src/payment.rs @@ -0,0 +1,115 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +//! Types which describe payments in lightning. + +use alloc::vec::Vec; + +use core::borrow::Borrow; + +use bitcoin::hashes::{ + Hash as _, + sha256::Hash as Sha256, +}; + +// TODO: Once we switch to rust-bitcoin 0.32, import this as bitcoin::hex +use hex_conservative::display::impl_fmt_traits; + +/// The payment hash is the hash of the [`PaymentPreimage`] which is the value used to lock funds +/// in HTLCs while they transit the lightning network. +/// +/// This is not exported to bindings users as we just use [u8; 32] directly +#[derive(Hash, Copy, Clone, PartialEq, Eq, Ord, PartialOrd)] +pub struct PaymentHash(pub [u8; 32]); + +impl Borrow<[u8]> for PaymentHash { + fn borrow(&self) -> &[u8] { + &self.0[..] + } +} + +impl_fmt_traits! { + impl fmt_traits for PaymentHash { + const LENGTH: usize = 32; + } +} + +/// The payment preimage is the "secret key" which is used to claim the funds of an HTLC on-chain +/// or in a lightning channel. +/// +/// This is not exported to bindings users as we just use [u8; 32] directly +#[derive(Hash, Copy, Clone, PartialEq, Eq, Ord, PartialOrd)] +pub struct PaymentPreimage(pub [u8; 32]); + +impl Borrow<[u8]> for PaymentPreimage { + fn borrow(&self) -> &[u8] { + &self.0[..] + } +} + +impl_fmt_traits! { + impl fmt_traits for PaymentPreimage { + const LENGTH: usize = 32; + } +} + +/// Converts a `PaymentPreimage` into a `PaymentHash` by hashing the preimage with SHA256. +impl From for PaymentHash { + fn from(value: PaymentPreimage) -> Self { + PaymentHash(Sha256::hash(&value.0).to_byte_array()) + } +} + +/// The payment secret is used to authenticate the sender of an HTLC to the recipient and tie +/// multi-part HTLCs together into a single payment. +/// +/// This is not exported to bindings users as we just use [u8; 32] directly +#[derive(Hash, Copy, Clone, PartialEq, Eq, Ord, PartialOrd)] +pub struct PaymentSecret(pub [u8; 32]); + +impl Borrow<[u8]> for PaymentSecret { + fn borrow(&self) -> &[u8] { + &self.0[..] + } +} + +impl_fmt_traits! { + impl fmt_traits for PaymentSecret { + const LENGTH: usize = 32; + } +} + +use bech32::{Base32Len, FromBase32, ToBase32, WriteBase32, u5}; + +impl FromBase32 for PaymentSecret { + type Err = bech32::Error; + + fn from_base32(field_data: &[u5]) -> Result { + if field_data.len() != 52 { + return Err(bech32::Error::InvalidLength) + } else { + let data_bytes = Vec::::from_base32(field_data)?; + let mut payment_secret = [0; 32]; + payment_secret.copy_from_slice(&data_bytes); + Ok(PaymentSecret(payment_secret)) + } + } +} + +impl ToBase32 for PaymentSecret { + fn write_base32(&self, writer: &mut W) -> Result<(), ::Err> { + (&self.0[..]).write_base32(writer) + } +} + +impl Base32Len for PaymentSecret { + fn base32_len(&self) -> usize { + 52 + } +} diff --git a/lightning/Cargo.toml b/lightning/Cargo.toml index a0f1e4027f6..389ab087cc5 100644 --- a/lightning/Cargo.toml +++ b/lightning/Cargo.toml @@ -40,6 +40,8 @@ grind_signatures = [] default = ["std", "grind_signatures"] [dependencies] +lightning-types = { version = "0.1", path = "../lightning-types", default-features = false } + bech32 = { version = "0.9.1", default-features = false } bitcoin = { version = "0.31.2", default-features = false, features = ["secp-recovery"] } diff --git a/lightning/src/lib.rs b/lightning/src/lib.rs index 54652e32015..54e394ced46 100644 --- a/lightning/src/lib.rs +++ b/lightning/src/lib.rs @@ -61,6 +61,9 @@ compile_error!("Tests will always fail with cfg=fuzzing"); #[macro_use] extern crate alloc; + +extern crate lightning_types; + pub extern crate bitcoin; #[cfg(any(test, feature = "std"))] extern crate core; diff --git a/lightning/src/ln/mod.rs b/lightning/src/ln/mod.rs index 888044b7aea..9bac19104bf 100644 --- a/lightning/src/ln/mod.rs +++ b/lightning/src/ln/mod.rs @@ -25,7 +25,7 @@ pub mod features; pub mod script; pub mod types; -pub use types::{ChannelId, PaymentHash, PaymentPreimage, PaymentSecret}; +pub use lightning_types::payment::{PaymentHash, PaymentPreimage, PaymentSecret}; #[cfg(fuzzing)] pub mod peer_channel_encryptor; diff --git a/lightning/src/ln/types.rs b/lightning/src/ln/types.rs index b813239786a..d5ab05505bb 100644 --- a/lightning/src/ln/types.rs +++ b/lightning/src/ln/types.rs @@ -121,75 +121,7 @@ impl fmt::Display for ChannelId { } } - -/// The payment hash is the hash of the [`PaymentPreimage`] which is the value used to lock funds -/// in HTLCs while they transit the lightning network. -/// -/// This is not exported to bindings users as we just use [u8; 32] directly -#[derive(Hash, Copy, Clone, PartialEq, Eq, Debug, Ord, PartialOrd)] -pub struct PaymentHash(pub [u8; 32]); - -impl core::fmt::Display for PaymentHash { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - crate::util::logger::DebugBytes(&self.0).fmt(f) - } -} - -/// The payment preimage is the "secret key" which is used to claim the funds of an HTLC on-chain -/// or in a lightning channel. -/// -/// This is not exported to bindings users as we just use [u8; 32] directly -#[derive(Hash, Copy, Clone, PartialEq, Eq, Debug, Ord, PartialOrd)] -pub struct PaymentPreimage(pub [u8; 32]); - -impl core::fmt::Display for PaymentPreimage { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - crate::util::logger::DebugBytes(&self.0).fmt(f) - } -} - -/// Converts a `PaymentPreimage` into a `PaymentHash` by hashing the preimage with SHA256. -impl From for PaymentHash { - fn from(value: PaymentPreimage) -> Self { - PaymentHash(Sha256::hash(&value.0).to_byte_array()) - } -} - -/// The payment secret is used to authenticate the sender of an HTLC to the recipient and tie -/// multi-part HTLCs together into a single payment. -/// -/// This is not exported to bindings users as we just use [u8; 32] directly -#[derive(Hash, Copy, Clone, PartialEq, Eq, Debug, Ord, PartialOrd)] -pub struct PaymentSecret(pub [u8; 32]); - -use bech32::{Base32Len, FromBase32, ToBase32, WriteBase32, u5}; - -impl FromBase32 for PaymentSecret { - type Err = bech32::Error; - - fn from_base32(field_data: &[u5]) -> Result { - if field_data.len() != 52 { - return Err(bech32::Error::InvalidLength) - } else { - let data_bytes = Vec::::from_base32(field_data)?; - let mut payment_secret = [0; 32]; - payment_secret.copy_from_slice(&data_bytes); - Ok(PaymentSecret(payment_secret)) - } - } -} - -impl ToBase32 for PaymentSecret { - fn write_base32(&self, writer: &mut W) -> Result<(), ::Err> { - (&self.0[..]).write_base32(writer) - } -} - -impl Base32Len for PaymentSecret { - fn base32_len(&self) -> usize { - 52 - } -} +pub use lightning_types::payment::{PaymentHash, PaymentPreimage, PaymentSecret}; #[cfg(test)] mod tests { From b97d742f91fdb9cee8ecccb10f1fef3edb5c4600 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Fri, 9 Aug 2024 00:29:25 +0000 Subject: [PATCH 02/11] Move `Rout{ingFees,eHint{,Hop}}` to `lightning-types` `lightning-invoice` currently has a dependency on the entire `lightning` crate just because it wants to use some of the useful types from it. This is obviously backwards and leads to some awkwardness like the BOLT 11 invoice signing API in the `lightning` crate taking a `[u5]` rather than a `Bolt11Invoice`. This takes one more step, moving the routing types `lightning-invoice` uses into `lightning-types`. --- lightning-types/src/lib.rs | 1 + lightning-types/src/routing.rs | 50 +++++++++++++++++++++++++++++++++ lightning/src/routing/gossip.rs | 12 ++------ lightning/src/routing/router.rs | 31 ++++---------------- 4 files changed, 58 insertions(+), 36 deletions(-) create mode 100644 lightning-types/src/routing.rs diff --git a/lightning-types/src/lib.rs b/lightning-types/src/lib.rs index 0174bcc9b3d..aa21ec7cd59 100644 --- a/lightning-types/src/lib.rs +++ b/lightning-types/src/lib.rs @@ -24,3 +24,4 @@ extern crate alloc; extern crate core; pub mod payment; +pub mod routing; diff --git a/lightning-types/src/routing.rs b/lightning-types/src/routing.rs new file mode 100644 index 00000000000..2335af2f1c3 --- /dev/null +++ b/lightning-types/src/routing.rs @@ -0,0 +1,50 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +//! Various types which describe routes or information about partial routes within the lightning +//! network. + +use alloc::vec::Vec; + +use bitcoin::secp256k1::PublicKey; + +/// Fees for routing via a given channel or a node +#[derive(Eq, PartialEq, Copy, Clone, Debug, Hash, Ord, PartialOrd)] +pub struct RoutingFees { + /// Flat routing fee in millisatoshis. + pub base_msat: u32, + /// Liquidity-based routing fee in millionths of a routed amount. + /// In other words, 10000 is 1%. + pub proportional_millionths: u32, +} + +/// A list of hops along a payment path terminating with a channel to the recipient. +#[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub struct RouteHint(pub Vec); + +/// A channel descriptor for a hop along a payment path. +/// +/// While this generally comes from BOLT 11's `r` field, this struct includes more fields than are +/// available in BOLT 11. Thus, encoding and decoding this via `lightning-invoice` is lossy, as +/// fields not supported in BOLT 11 will be stripped. +#[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub struct RouteHintHop { + /// The node_id of the non-target end of the route + pub src_node_id: PublicKey, + /// The short_channel_id of this channel + pub short_channel_id: u64, + /// The fees which must be paid to use this channel + pub fees: RoutingFees, + /// The difference in CLTV values between this node and the next node. + pub cltv_expiry_delta: u16, + /// The minimum value, in msat, which must be relayed to the next hop. + pub htlc_minimum_msat: Option, + /// The maximum value in msat available for routing with a single HTLC. + pub htlc_maximum_msat: Option, +} diff --git a/lightning/src/routing/gossip.rs b/lightning/src/routing/gossip.rs index 31f93db905c..22a3f6e7980 100644 --- a/lightning/src/routing/gossip.rs +++ b/lightning/src/routing/gossip.rs @@ -45,6 +45,8 @@ use core::str::FromStr; use core::sync::atomic::{AtomicUsize, Ordering}; use core::{cmp, fmt}; +pub use lightning_types::routing::RoutingFees; + #[cfg(feature = "std")] use std::time::{SystemTime, UNIX_EPOCH}; @@ -1212,16 +1214,6 @@ impl EffectiveCapacity { } } -/// Fees for routing via a given channel or a node -#[derive(Eq, PartialEq, Copy, Clone, Debug, Hash, Ord, PartialOrd)] -pub struct RoutingFees { - /// Flat routing fee in millisatoshis. - pub base_msat: u32, - /// Liquidity-based routing fee in millionths of a routed amount. - /// In other words, 10000 is 1%. - pub proportional_millionths: u32, -} - impl_writeable_tlv_based!(RoutingFees, { (0, base_msat, required), (2, proportional_millionths, required) diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index dac9ada3800..bf894f0ebb6 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -22,7 +22,7 @@ use crate::ln::msgs::{DecodeError, ErrorAction, LightningError, MAX_VALUE_MSAT}; use crate::ln::onion_utils; use crate::offers::invoice::{BlindedPayInfo, Bolt12Invoice}; use crate::onion_message::messenger::{DefaultMessageRouter, Destination, MessageRouter, OnionMessagePath}; -use crate::routing::gossip::{DirectedChannelInfo, EffectiveCapacity, ReadOnlyNetworkGraph, NetworkGraph, NodeId, RoutingFees}; +use crate::routing::gossip::{DirectedChannelInfo, EffectiveCapacity, ReadOnlyNetworkGraph, NetworkGraph, NodeId}; use crate::routing::scoring::{ChannelUsage, LockableScore, ScoreLookUp}; use crate::sign::EntropySource; use crate::util::ser::{Writeable, Readable, ReadableArgs, Writer}; @@ -35,6 +35,10 @@ use alloc::collections::BinaryHeap; use core::{cmp, fmt}; use core::ops::Deref; +use lightning_types::routing::RoutingFees; + +pub use lightning_types::routing::{RouteHint, RouteHintHop}; + /// A [`Router`] implemented using [`find_route`]. /// /// # Privacy @@ -1099,10 +1103,6 @@ impl ReadableArgs for Features { } } -/// A list of hops along a payment path terminating with a channel to the recipient. -#[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] -pub struct RouteHint(pub Vec); - impl Writeable for RouteHint { fn write(&self, writer: &mut W) -> Result<(), io::Error> { (self.0.len() as u64).write(writer)?; @@ -1124,27 +1124,6 @@ impl Readable for RouteHint { } } -/// A channel descriptor for a hop along a payment path. -/// -/// While this generally comes from BOLT 11's `r` field, this struct includes more fields than are -/// available in BOLT 11. Thus, encoding and decoding this via `lightning-invoice` is lossy, as -/// fields not supported in BOLT 11 will be stripped. -#[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] -pub struct RouteHintHop { - /// The node_id of the non-target end of the route - pub src_node_id: PublicKey, - /// The short_channel_id of this channel - pub short_channel_id: u64, - /// The fees which must be paid to use this channel - pub fees: RoutingFees, - /// The difference in CLTV values between this node and the next node. - pub cltv_expiry_delta: u16, - /// The minimum value, in msat, which must be relayed to the next hop. - pub htlc_minimum_msat: Option, - /// The maximum value in msat available for routing with a single HTLC. - pub htlc_maximum_msat: Option, -} - impl_writeable_tlv_based!(RouteHintHop, { (0, src_node_id, required), (1, htlc_minimum_msat, option), From 954b7be85a63c9a942056a33443bd7979b14dcc9 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Fri, 9 Aug 2024 14:18:09 +0000 Subject: [PATCH 03/11] Replace usages of `Features::is_subset` and remove it It turns out all the places we use `Features::is_subset` we could as well be using `Features::requires_unknown_bits_from`. Further, in the next commit `Features` will move to a different crate so any methods which the `lightning` crate uses will need to be public. As the `is_subset` API is prety confusing (it doesn't consider optional/required bits, only whether the bits themselves are strictly a subset) it'd be nice to not have to expose it, which is enabled here. --- lightning/src/chain/package.rs | 2 +- lightning/src/ln/chan_utils.rs | 8 +++++--- lightning/src/ln/channel.rs | 7 ++++--- lightning/src/ln/features.rs | 18 ------------------ 4 files changed, 10 insertions(+), 25 deletions(-) diff --git a/lightning/src/chain/package.rs b/lightning/src/chain/package.rs index b0dfca4c207..361e12fa563 100644 --- a/lightning/src/chain/package.rs +++ b/lightning/src/chain/package.rs @@ -93,7 +93,7 @@ pub(crate) fn verify_channel_type_features(channel_type_features: &Option ChannelContext where SP::Target: SignerProvider { } let channel_type = get_initial_channel_type(&config, their_features); - debug_assert!(channel_type.is_subset(&channelmanager::provided_channel_type_features(&config))); + debug_assert!(!channel_type.supports_any_optional_bits()); + debug_assert!(!channel_type.requires_unknown_bits_from(&channelmanager::provided_channel_type_features(&config))); let (commitment_conf_target, anchor_outputs_value_msat) = if channel_type.supports_anchors_zero_fee_htlc_tx() { (ConfirmationTarget::AnchorChannelFee, ANCHOR_OUTPUT_VALUE_SATOSHI * 2 * 1000) @@ -7967,7 +7968,7 @@ pub(super) fn channel_type_from_open_channel( return Err(ChannelError::close("Channel Type was not understood - we require static remote key".to_owned())); } // Make sure we support all of the features behind the channel type. - if !channel_type.is_subset(our_supported_features) { + if channel_type.requires_unknown_bits_from(&our_supported_features) { return Err(ChannelError::close("Channel Type contains unsupported features".to_owned())); } let announced_channel = if (common_fields.channel_flags & 1) == 1 { true } else { false }; @@ -9355,7 +9356,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch } let chan_features = channel_type.as_ref().unwrap(); - if !chan_features.is_subset(our_supported_features) { + if chan_features.supports_any_optional_bits() || chan_features.requires_unknown_bits_from(&our_supported_features) { // If the channel was written by a new version and negotiated with features we don't // understand yet, refuse to read it. return Err(DecodeError::UnknownRequiredFeature); diff --git a/lightning/src/ln/features.rs b/lightning/src/ln/features.rs index 79bf871de72..3b041cbc5c2 100644 --- a/lightning/src/ln/features.rs +++ b/lightning/src/ln/features.rs @@ -834,24 +834,6 @@ impl Features { }) } - // Returns true if the features within `self` are a subset of the features within `other`. - pub(crate) fn is_subset(&self, other: &Self) -> bool { - for (idx, byte) in self.flags.iter().enumerate() { - if let Some(other_byte) = other.flags.get(idx) { - if byte & other_byte != *byte { - // `self` has bits set that `other` doesn't. - return false; - } - } else { - if *byte > 0 { - // `self` has a non-zero byte that `other` doesn't. - return false; - } - } - } - true - } - /// Sets a required feature bit. Errors if `bit` is outside the feature range as defined /// by [BOLT 9]. /// From 0c5922e92a3ec3fdf9226cceeb9057ad9aa9dc19 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Fri, 9 Aug 2024 14:20:50 +0000 Subject: [PATCH 04/11] Move `Features` into `lightning-types` `lightning-invoice` currently has a dependency on the entire `lightning` crate just because it wants to use some of the useful types from it. This is obviously backwards and leads to some awkwardness like the BOLT 11 invoice signing API in the `lightning` crate taking a `[u5]` rather than a `Bolt11Invoice`. This takes one more step, moving the `Features` types from `lightning` to `lightning-types`. --- lightning-types/Cargo.toml | 1 + lightning-types/src/features.rs | 1202 +++++++++++++++++++++++++++++ lightning-types/src/lib.rs | 1 + lightning/Cargo.toml | 3 +- lightning/src/ln/features.rs | 1246 +------------------------------ lightning/src/ln/msgs.rs | 17 +- 6 files changed, 1256 insertions(+), 1214 deletions(-) create mode 100644 lightning-types/src/features.rs diff --git a/lightning-types/Cargo.toml b/lightning-types/Cargo.toml index 6bdd65d42a9..a063176ae57 100644 --- a/lightning-types/Cargo.toml +++ b/lightning-types/Cargo.toml @@ -13,6 +13,7 @@ edition = "2021" rustdoc-args = ["--cfg", "docsrs"] [features] +_test_utils = [] [dependencies] bitcoin = { version = "0.31", default-features = false } diff --git a/lightning-types/src/features.rs b/lightning-types/src/features.rs new file mode 100644 index 00000000000..613bfcf8d70 --- /dev/null +++ b/lightning-types/src/features.rs @@ -0,0 +1,1202 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +//! Feature flag definitions for the Lightning protocol according to [BOLT #9]. +//! +//! Lightning nodes advertise a supported set of operation through feature flags. Features are +//! applicable for a specific context. [`Features`] encapsulates behavior for specifying and +//! checking feature flags for a particular context. Each feature is defined internally by a trait +//! specifying the corresponding flags (i.e., even and odd bits). +//! +//! Whether a feature is considered "known" or "unknown" is relative to the implementation, whereas +//! the term "supports" is used in reference to a particular set of [`Features`]. That is, a node +//! supports a feature if it advertises the feature (as either required or optional) to its peers. +//! And the implementation can interpret a feature if the feature is known to it. +//! +//! The following features are currently required in the LDK: +//! - `VariableLengthOnion` - requires/supports variable-length routing onion payloads +//! (see [BOLT-4](https://github.com/lightning/bolts/blob/master/04-onion-routing.md) for more information). +//! - `StaticRemoteKey` - requires/supports static key for remote output +//! (see [BOLT-3](https://github.com/lightning/bolts/blob/master/03-transactions.md) for more information). +//! +//! The following features are currently supported in the LDK: +//! - `DataLossProtect` - requires/supports that a node which has somehow fallen behind, e.g., has been restored from an old backup, +//! can detect that it has fallen behind +//! (see [BOLT-2](https://github.com/lightning/bolts/blob/master/02-peer-protocol.md) for more information). +//! - `InitialRoutingSync` - requires/supports that the sending node needs a complete routing information dump +//! (see [BOLT-7](https://github.com/lightning/bolts/blob/master/07-routing-gossip.md#initial-sync) for more information). +//! - `UpfrontShutdownScript` - commits to a shutdown scriptpubkey when opening a channel +//! (see [BOLT-2](https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#the-open_channel-message) for more information). +//! - `GossipQueries` - requires/supports more sophisticated gossip control +//! (see [BOLT-7](https://github.com/lightning/bolts/blob/master/07-routing-gossip.md) for more information). +//! - `PaymentSecret` - requires/supports that a node supports payment_secret field +//! (see [BOLT-4](https://github.com/lightning/bolts/blob/master/04-onion-routing.md) for more information). +//! - `BasicMPP` - requires/supports that a node can receive basic multi-part payments +//! (see [BOLT-4](https://github.com/lightning/bolts/blob/master/04-onion-routing.md#basic-multi-part-payments) for more information). +//! - `Wumbo` - requires/supports that a node create large channels. Called `option_support_large_channel` in the spec. +//! (see [BOLT-2](https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#the-open_channel-message) for more information). +//! - `AnchorsZeroFeeHtlcTx` - requires/supports that commitment transactions include anchor outputs +//! and HTLC transactions are pre-signed with zero fee (see +//! [BOLT-3](https://github.com/lightning/bolts/blob/master/03-transactions.md) for more +//! information). +//! - `RouteBlinding` - requires/supports that a node can relay payments over blinded paths +//! (see [BOLT-4](https://github.com/lightning/bolts/blob/master/04-onion-routing.md#route-blinding) for more information). +//! - `ShutdownAnySegwit` - requires/supports that future segwit versions are allowed in `shutdown` +//! (see [BOLT-2](https://github.com/lightning/bolts/blob/master/02-peer-protocol.md) for more information). +//! - `OnionMessages` - requires/supports forwarding onion messages +//! (see [BOLT-7](https://github.com/lightning/bolts/pull/759/files) for more information). +// TODO: update link +//! - `ChannelType` - node supports the channel_type field in open/accept +//! (see [BOLT-2](https://github.com/lightning/bolts/blob/master/02-peer-protocol.md) for more information). +//! - `SCIDPrivacy` - supply channel aliases for routing +//! (see [BOLT-2](https://github.com/lightning/bolts/blob/master/02-peer-protocol.md) for more information). +//! - `PaymentMetadata` - include additional data in invoices which is passed to recipients in the +//! onion. +//! (see [BOLT-11](https://github.com/lightning/bolts/blob/master/11-payment-encoding.md) for +//! more). +//! - `ZeroConf` - supports accepting HTLCs and using channels prior to funding confirmation +//! (see +//! [BOLT-2](https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#the-channel_ready-message) +//! for more info). +//! - `Keysend` - send funds to a node without an invoice +//! (see the [`Keysend` feature assignment proposal](https://github.com/lightning/bolts/issues/605#issuecomment-606679798) for more information). +//! - `Trampoline` - supports receiving and forwarding Trampoline payments +//! (see the [`Trampoline` feature proposal](https://github.com/lightning/bolts/pull/836) for more information). +//! +//! LDK knows about the following features, but does not support them: +//! - `AnchorsNonzeroFeeHtlcTx` - the initial version of anchor outputs, which was later found to be +//! vulnerable (see this +//! [mailing list post](https://lists.linuxfoundation.org/pipermail/lightning-dev/2020-September/002796.html) +//! for more information). +//! +//! [BOLT #9]: https://github.com/lightning/bolts/blob/master/09-features.md + +use core::{cmp, fmt}; +use core::borrow::Borrow; +use core::hash::{Hash, Hasher}; +use core::marker::PhantomData; + +use alloc::vec::Vec; +use alloc::vec; + +use bech32::{Base32Len, FromBase32, ToBase32, u5, WriteBase32}; + +mod sealed { + use super::*; + + /// The context in which [`Features`] are applicable. Defines which features are known to the + /// implementation, though specification of them as required or optional is up to the code + /// constructing a features object. + pub trait Context { + /// Bitmask for selecting features that are known to the implementation. + const KNOWN_FEATURE_MASK: &'static [u8]; + } + + /// Defines a [`Context`] by stating which features it requires and which are optional. Features + /// are specified as a comma-separated list of bytes where each byte is a pipe-delimited list of + /// feature identifiers. + macro_rules! define_context { + ($context: ident, [$( $( $known_feature: ident )|*, )*]) => { + #[derive(Eq, PartialEq)] + pub struct $context {} + + impl Context for $context { + const KNOWN_FEATURE_MASK: &'static [u8] = &[ + $( + 0b00_00_00_00 $(| + ::REQUIRED_MASK | + ::OPTIONAL_MASK)*, + )* + ]; + } + + impl alloc::fmt::Display for Features<$context> { + fn fmt(&self, fmt: &mut alloc::fmt::Formatter) -> Result<(), alloc::fmt::Error> { + $( + $( + fmt.write_fmt(format_args!("{}: {}, ", stringify!($known_feature), + if <$context as $known_feature>::requires_feature(&self.flags) { "required" } + else if <$context as $known_feature>::supports_feature(&self.flags) { "supported" } + else { "not supported" }))?; + )* + {} // Rust gets mad if we only have a $()* block here, so add a dummy {} + )* + fmt.write_fmt(format_args!("unknown flags: {}", + if self.requires_unknown_bits() { "required" } + else if self.supports_unknown_bits() { "supported" } else { "none" })) + } + } + }; + } + + define_context!(InitContext, [ + // Byte 0 + DataLossProtect | InitialRoutingSync | UpfrontShutdownScript | GossipQueries, + // Byte 1 + VariableLengthOnion | StaticRemoteKey | PaymentSecret, + // Byte 2 + BasicMPP | Wumbo | AnchorsNonzeroFeeHtlcTx | AnchorsZeroFeeHtlcTx, + // Byte 3 + RouteBlinding | ShutdownAnySegwit | Taproot, + // Byte 4 + OnionMessages, + // Byte 5 + ChannelType | SCIDPrivacy, + // Byte 6 + ZeroConf, + // Byte 7 + Trampoline, + ]); + define_context!(NodeContext, [ + // Byte 0 + DataLossProtect | UpfrontShutdownScript | GossipQueries, + // Byte 1 + VariableLengthOnion | StaticRemoteKey | PaymentSecret, + // Byte 2 + BasicMPP | Wumbo | AnchorsNonzeroFeeHtlcTx | AnchorsZeroFeeHtlcTx, + // Byte 3 + RouteBlinding | ShutdownAnySegwit | Taproot, + // Byte 4 + OnionMessages, + // Byte 5 + ChannelType | SCIDPrivacy, + // Byte 6 + ZeroConf | Keysend, + // Byte 7 + Trampoline, + ]); + define_context!(ChannelContext, []); + define_context!(Bolt11InvoiceContext, [ + // Byte 0 + , + // Byte 1 + VariableLengthOnion | PaymentSecret, + // Byte 2 + BasicMPP, + // Byte 3 + , + // Byte 4 + , + // Byte 5 + , + // Byte 6 + PaymentMetadata, + // Byte 7 + Trampoline, + ]); + define_context!(OfferContext, []); + define_context!(InvoiceRequestContext, []); + define_context!(Bolt12InvoiceContext, [ + // Byte 0 + , + // Byte 1 + , + // Byte 2 + BasicMPP, + ]); + define_context!(BlindedHopContext, []); + // This isn't a "real" feature context, and is only used in the channel_type field in an + // `OpenChannel` message. + define_context!(ChannelTypeContext, [ + // Byte 0 + , + // Byte 1 + StaticRemoteKey, + // Byte 2 + AnchorsNonzeroFeeHtlcTx | AnchorsZeroFeeHtlcTx, + // Byte 3 + Taproot, + // Byte 4 + , + // Byte 5 + SCIDPrivacy, + // Byte 6 + ZeroConf, + ]); + + /// Defines a feature with the given bits for the specified [`Context`]s. The generated trait is + /// useful for manipulating feature flags. + macro_rules! define_feature { + ($odd_bit: expr, $feature: ident, [$($context: ty),+], $doc: expr, $optional_setter: ident, + $required_setter: ident, $supported_getter: ident) => { + #[doc = $doc] + /// + /// See [BOLT #9] for details. + /// + /// [BOLT #9]: https://github.com/lightning/bolts/blob/master/09-features.md + pub trait $feature: Context { + /// The bit used to signify that the feature is required. + const EVEN_BIT: usize = $odd_bit - 1; + + /// The bit used to signify that the feature is optional. + const ODD_BIT: usize = $odd_bit; + + /// Assertion that [`EVEN_BIT`] is actually even. + /// + /// [`EVEN_BIT`]: #associatedconstant.EVEN_BIT + const ASSERT_EVEN_BIT_PARITY: usize; + + /// Assertion that [`ODD_BIT`] is actually odd. + /// + /// [`ODD_BIT`]: #associatedconstant.ODD_BIT + const ASSERT_ODD_BIT_PARITY: usize; + + /// Assertion that the bits are set in the context's [`KNOWN_FEATURE_MASK`]. + /// + /// [`KNOWN_FEATURE_MASK`]: Context::KNOWN_FEATURE_MASK + #[cfg(not(any(test, feature = "_test_utils")))] // We violate this constraint with `UnknownFeature` + const ASSERT_BITS_IN_MASK: u8; + + /// The byte where the feature is set. + const BYTE_OFFSET: usize = Self::EVEN_BIT / 8; + + /// The bitmask for the feature's required flag relative to the [`BYTE_OFFSET`]. + /// + /// [`BYTE_OFFSET`]: #associatedconstant.BYTE_OFFSET + const REQUIRED_MASK: u8 = 1 << (Self::EVEN_BIT - 8 * Self::BYTE_OFFSET); + + /// The bitmask for the feature's optional flag relative to the [`BYTE_OFFSET`]. + /// + /// [`BYTE_OFFSET`]: #associatedconstant.BYTE_OFFSET + const OPTIONAL_MASK: u8 = 1 << (Self::ODD_BIT - 8 * Self::BYTE_OFFSET); + + /// Returns whether the feature is required by the given flags. + #[inline] + fn requires_feature(flags: &Vec) -> bool { + flags.len() > Self::BYTE_OFFSET && + (flags[Self::BYTE_OFFSET] & Self::REQUIRED_MASK) != 0 + } + + /// Returns whether the feature is supported by the given flags. + #[inline] + fn supports_feature(flags: &Vec) -> bool { + flags.len() > Self::BYTE_OFFSET && + (flags[Self::BYTE_OFFSET] & (Self::REQUIRED_MASK | Self::OPTIONAL_MASK)) != 0 + } + + /// Sets the feature's required (even) bit in the given flags. + #[inline] + fn set_required_bit(flags: &mut Vec) { + if flags.len() <= Self::BYTE_OFFSET { + flags.resize(Self::BYTE_OFFSET + 1, 0u8); + } + + flags[Self::BYTE_OFFSET] |= Self::REQUIRED_MASK; + flags[Self::BYTE_OFFSET] &= !Self::OPTIONAL_MASK; + } + + /// Sets the feature's optional (odd) bit in the given flags. + #[inline] + fn set_optional_bit(flags: &mut Vec) { + if flags.len() <= Self::BYTE_OFFSET { + flags.resize(Self::BYTE_OFFSET + 1, 0u8); + } + + flags[Self::BYTE_OFFSET] |= Self::OPTIONAL_MASK; + } + + /// Clears the feature's required (even) and optional (odd) bits from the given + /// flags. + #[inline] + fn clear_bits(flags: &mut Vec) { + if flags.len() > Self::BYTE_OFFSET { + flags[Self::BYTE_OFFSET] &= !Self::REQUIRED_MASK; + flags[Self::BYTE_OFFSET] &= !Self::OPTIONAL_MASK; + } + + let last_non_zero_byte = flags.iter().rposition(|&byte| byte != 0); + let size = if let Some(offset) = last_non_zero_byte { offset + 1 } else { 0 }; + flags.resize(size, 0u8); + } + } + + impl Features { + /// Set this feature as optional. + pub fn $optional_setter(&mut self) { + ::set_optional_bit(&mut self.flags); + } + + /// Set this feature as required. + pub fn $required_setter(&mut self) { + ::set_required_bit(&mut self.flags); + } + + /// Checks if this feature is supported. + pub fn $supported_getter(&self) -> bool { + ::supports_feature(&self.flags) + } + } + + $( + impl $feature for $context { + // EVEN_BIT % 2 == 0 + const ASSERT_EVEN_BIT_PARITY: usize = 0 - (::EVEN_BIT % 2); + + // ODD_BIT % 2 == 1 + const ASSERT_ODD_BIT_PARITY: usize = (::ODD_BIT % 2) - 1; + + // (byte & (REQUIRED_MASK | OPTIONAL_MASK)) >> (EVEN_BIT % 8) == 3 + #[cfg(not(any(test, feature = "_test_utils")))] // We violate this constraint with `UnknownFeature` + const ASSERT_BITS_IN_MASK: u8 = + ((<$context>::KNOWN_FEATURE_MASK[::BYTE_OFFSET] & (::REQUIRED_MASK | ::OPTIONAL_MASK)) + >> (::EVEN_BIT % 8)) - 3; + } + )* + }; + ($odd_bit: expr, $feature: ident, [$($context: ty),+], $doc: expr, $optional_setter: ident, + $required_setter: ident, $supported_getter: ident, $required_getter: ident) => { + define_feature!($odd_bit, $feature, [$($context),+], $doc, $optional_setter, $required_setter, $supported_getter); + impl Features { + /// Checks if this feature is required. + pub fn $required_getter(&self) -> bool { + ::requires_feature(&self.flags) + } + } + } + } + + define_feature!(1, DataLossProtect, [InitContext, NodeContext], + "Feature flags for `option_data_loss_protect`.", set_data_loss_protect_optional, + set_data_loss_protect_required, supports_data_loss_protect, requires_data_loss_protect); + // NOTE: Per Bolt #9, initial_routing_sync has no even bit. + define_feature!(3, InitialRoutingSync, [InitContext], "Feature flags for `initial_routing_sync`.", + set_initial_routing_sync_optional, set_initial_routing_sync_required, + initial_routing_sync); + define_feature!(5, UpfrontShutdownScript, [InitContext, NodeContext], + "Feature flags for `option_upfront_shutdown_script`.", set_upfront_shutdown_script_optional, + set_upfront_shutdown_script_required, supports_upfront_shutdown_script, + requires_upfront_shutdown_script); + define_feature!(7, GossipQueries, [InitContext, NodeContext], + "Feature flags for `gossip_queries`.", set_gossip_queries_optional, set_gossip_queries_required, + supports_gossip_queries, requires_gossip_queries); + define_feature!(9, VariableLengthOnion, [InitContext, NodeContext, Bolt11InvoiceContext], + "Feature flags for `var_onion_optin`.", set_variable_length_onion_optional, + set_variable_length_onion_required, supports_variable_length_onion, + requires_variable_length_onion); + define_feature!(13, StaticRemoteKey, [InitContext, NodeContext, ChannelTypeContext], + "Feature flags for `option_static_remotekey`.", set_static_remote_key_optional, + set_static_remote_key_required, supports_static_remote_key, requires_static_remote_key); + define_feature!(15, PaymentSecret, [InitContext, NodeContext, Bolt11InvoiceContext], + "Feature flags for `payment_secret`.", set_payment_secret_optional, set_payment_secret_required, + supports_payment_secret, requires_payment_secret); + define_feature!(17, BasicMPP, [InitContext, NodeContext, Bolt11InvoiceContext, Bolt12InvoiceContext], + "Feature flags for `basic_mpp`.", set_basic_mpp_optional, set_basic_mpp_required, + supports_basic_mpp, requires_basic_mpp); + define_feature!(19, Wumbo, [InitContext, NodeContext], + "Feature flags for `option_support_large_channel` (aka wumbo channels).", set_wumbo_optional, set_wumbo_required, + supports_wumbo, requires_wumbo); + define_feature!(21, AnchorsNonzeroFeeHtlcTx, [InitContext, NodeContext, ChannelTypeContext], + "Feature flags for `option_anchors_nonzero_fee_htlc_tx`.", set_anchors_nonzero_fee_htlc_tx_optional, + set_anchors_nonzero_fee_htlc_tx_required, supports_anchors_nonzero_fee_htlc_tx, requires_anchors_nonzero_fee_htlc_tx); + define_feature!(23, AnchorsZeroFeeHtlcTx, [InitContext, NodeContext, ChannelTypeContext], + "Feature flags for `option_anchors_zero_fee_htlc_tx`.", set_anchors_zero_fee_htlc_tx_optional, + set_anchors_zero_fee_htlc_tx_required, supports_anchors_zero_fee_htlc_tx, requires_anchors_zero_fee_htlc_tx); + define_feature!(25, RouteBlinding, [InitContext, NodeContext], + "Feature flags for `option_route_blinding`.", set_route_blinding_optional, + set_route_blinding_required, supports_route_blinding, requires_route_blinding); + define_feature!(27, ShutdownAnySegwit, [InitContext, NodeContext], + "Feature flags for `opt_shutdown_anysegwit`.", set_shutdown_any_segwit_optional, + set_shutdown_any_segwit_required, supports_shutdown_anysegwit, requires_shutdown_anysegwit); + define_feature!(31, Taproot, [InitContext, NodeContext, ChannelTypeContext], + "Feature flags for `option_taproot`.", set_taproot_optional, + set_taproot_required, supports_taproot, requires_taproot); + define_feature!(39, OnionMessages, [InitContext, NodeContext], + "Feature flags for `option_onion_messages`.", set_onion_messages_optional, + set_onion_messages_required, supports_onion_messages, requires_onion_messages); + define_feature!(45, ChannelType, [InitContext, NodeContext], + "Feature flags for `option_channel_type`.", set_channel_type_optional, + set_channel_type_required, supports_channel_type, requires_channel_type); + define_feature!(47, SCIDPrivacy, [InitContext, NodeContext, ChannelTypeContext], + "Feature flags for only forwarding with SCID aliasing. Called `option_scid_alias` in the BOLTs", + set_scid_privacy_optional, set_scid_privacy_required, supports_scid_privacy, requires_scid_privacy); + define_feature!(49, PaymentMetadata, [Bolt11InvoiceContext], + "Feature flags for payment metadata in invoices.", set_payment_metadata_optional, + set_payment_metadata_required, supports_payment_metadata, requires_payment_metadata); + define_feature!(51, ZeroConf, [InitContext, NodeContext, ChannelTypeContext], + "Feature flags for accepting channels with zero confirmations. Called `option_zeroconf` in the BOLTs", + set_zero_conf_optional, set_zero_conf_required, supports_zero_conf, requires_zero_conf); + define_feature!(55, Keysend, [NodeContext], + "Feature flags for keysend payments.", set_keysend_optional, set_keysend_required, + supports_keysend, requires_keysend); + define_feature!(57, Trampoline, [InitContext, NodeContext, Bolt11InvoiceContext], + "Feature flags for Trampoline routing.", set_trampoline_routing_optional, set_trampoline_routing_required, + supports_trampoline_routing, requires_trampoline_routing); + // Note: update the module-level docs when a new feature bit is added! + + #[cfg(any(test, feature = "_test_utils"))] + define_feature!(123456789, UnknownFeature, + [NodeContext, ChannelContext, Bolt11InvoiceContext, OfferContext, InvoiceRequestContext, Bolt12InvoiceContext, BlindedHopContext], + "Feature flags for an unknown feature used in testing.", set_unknown_feature_optional, + set_unknown_feature_required, supports_unknown_test_feature, requires_unknown_test_feature); +} + +const ANY_REQUIRED_FEATURES_MASK: u8 = 0b01_01_01_01; +const ANY_OPTIONAL_FEATURES_MASK: u8 = 0b10_10_10_10; + +/// Tracks the set of features which a node implements, templated by the context in which it +/// appears. +/// +/// This is not exported to bindings users as we map the concrete feature types below directly instead +#[derive(Eq)] +pub struct Features { + /// Note that, for convenience, flags is LITTLE endian (despite being big-endian on the wire) + flags: Vec, + mark: PhantomData, +} + +impl> core::ops::BitOrAssign for Features { + fn bitor_assign(&mut self, rhs: Rhs) { + let total_feature_len = cmp::max(self.flags.len(), rhs.borrow().flags.len()); + self.flags.resize(total_feature_len, 0u8); + for (byte, rhs_byte) in self.flags.iter_mut().zip(rhs.borrow().flags.iter()) { + *byte |= *rhs_byte; + } + } +} + +impl core::ops::BitOr for Features { + type Output = Self; + + fn bitor(mut self, o: Self) -> Self { + self |= o; + self + } +} + +impl Clone for Features { + fn clone(&self) -> Self { + Self { + flags: self.flags.clone(), + mark: PhantomData, + } + } +} +impl Hash for Features { + fn hash(&self, hasher: &mut H) { + let mut nonzero_flags = &self.flags[..]; + while nonzero_flags.last() == Some(&0) { + nonzero_flags = &nonzero_flags[..nonzero_flags.len() - 1]; + } + nonzero_flags.hash(hasher); + } +} +impl PartialEq for Features { + fn eq(&self, o: &Self) -> bool { + let mut o_iter = o.flags.iter(); + let mut self_iter = self.flags.iter(); + loop { + match (o_iter.next(), self_iter.next()) { + (Some(o), Some(us)) => if o != us { return false }, + (Some(b), None) | (None, Some(b)) => if *b != 0 { return false }, + (None, None) => return true, + } + } + } +} +impl PartialOrd for Features { + fn partial_cmp(&self, other: &Self) -> Option { + self.flags.partial_cmp(&other.flags) + } +} +impl Ord for Features { + fn cmp(&self, other: &Self) -> cmp::Ordering { + self.flags.cmp(&other.flags) + } +} +impl fmt::Debug for Features { + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + self.flags.fmt(fmt) + } +} + +/// Features used within an `init` message. +pub type InitFeatures = Features; +/// Features used within a `node_announcement` message. +pub type NodeFeatures = Features; +/// Features used within a `channel_announcement` message. +pub type ChannelFeatures = Features; +/// Features used within an invoice. +pub type Bolt11InvoiceFeatures = Features; +/// Features used within an `offer`. +pub type OfferFeatures = Features; +/// Features used within an `invoice_request`. +pub type InvoiceRequestFeatures = Features; +/// Features used within an `invoice`. +pub type Bolt12InvoiceFeatures = Features; +/// Features used within BOLT 4 encrypted_data_tlv and BOLT 12 blinded_payinfo +pub type BlindedHopFeatures = Features; + +/// Features used within the channel_type field in an OpenChannel message. +/// +/// A channel is always of some known "type", describing the transaction formats used and the exact +/// semantics of our interaction with our peer. +/// +/// Note that because a channel is a specific type which is proposed by the opener and accepted by +/// the counterparty, only required features are allowed here. +/// +/// This is serialized differently from other feature types - it is not prefixed by a length, and +/// thus must only appear inside a TLV where its length is known in advance. +pub type ChannelTypeFeatures = Features; + +impl InitFeatures { + #[doc(hidden)] + /// Converts `InitFeatures` to `Features`. Only known `InitFeatures` relevant to context `C` + /// are included in the result. + pub fn to_context(&self) -> Features { + self.to_context_internal() + } +} + +impl Bolt11InvoiceFeatures { + #[doc(hidden)] + /// Converts `Bolt11InvoiceFeatures` to `Features`. Only known `Bolt11InvoiceFeatures` relevant to + /// context `C` are included in the result. + pub fn to_context(&self) -> Features { + self.to_context_internal() + } + + /// Getting a route for a keysend payment to a private node requires providing the payee's + /// features (since they were not announced in a node announcement). However, keysend payments + /// don't have an invoice to pull the payee's features from, so this method is provided for use + /// when a [`Bolt11InvoiceFeatures`] is required in a route. + /// + /// MPP keysend is not widely supported yet, so we parameterize support to allow the user to + /// choose whether their router should find multi-part routes. + pub fn for_keysend(allow_mpp: bool) -> Bolt11InvoiceFeatures { + let mut res = Bolt11InvoiceFeatures::empty(); + res.set_variable_length_onion_optional(); + if allow_mpp { + res.set_basic_mpp_optional(); + } + res + } +} + +impl Bolt12InvoiceFeatures { + #[doc(hidden)] + /// Converts [`Bolt12InvoiceFeatures`] to [`Features`]. Only known [`Bolt12InvoiceFeatures`] + /// relevant to context `C` are included in the result. + pub fn to_context(&self) -> Features { + self.to_context_internal() + } +} + +impl ChannelTypeFeatures { + #[doc(hidden)] + // Maps the relevant `InitFeatures` to `ChannelTypeFeatures`. Any unknown features to + // `ChannelTypeFeatures` are not included in the result. + pub fn from_init(init: &InitFeatures) -> Self { + let mut ret = init.to_context_internal(); + // ChannelTypeFeatures must only contain required bits, so we OR the required forms of all + // optional bits and then AND out the optional ones. + for byte in ret.flags.iter_mut() { + *byte |= (*byte & ANY_OPTIONAL_FEATURES_MASK) >> 1; + *byte &= ANY_REQUIRED_FEATURES_MASK; + } + ret + } + + /// Constructs a ChannelTypeFeatures with only static_remotekey set + pub fn only_static_remote_key() -> Self { + let mut ret = Self::empty(); + ::set_required_bit(&mut ret.flags); + ret + } + + /// Constructs a ChannelTypeFeatures with anchors support + pub fn anchors_zero_htlc_fee_and_dependencies() -> Self { + let mut ret = Self::empty(); + ::set_required_bit(&mut ret.flags); + ::set_required_bit(&mut ret.flags); + ret + } +} + +impl ToBase32 for Bolt11InvoiceFeatures { + fn write_base32(&self, writer: &mut W) -> Result<(), ::Err> { + // Explanation for the "4": the normal way to round up when dividing is to add the divisor + // minus one before dividing + let length_u5s = (self.flags.len() * 8 + 4) / 5 as usize; + let mut res_u5s: Vec = vec![u5::try_from_u8(0).unwrap(); length_u5s]; + for (byte_idx, byte) in self.flags.iter().enumerate() { + let bit_pos_from_left_0_indexed = byte_idx * 8; + let new_u5_idx = length_u5s - (bit_pos_from_left_0_indexed / 5) as usize - 1; + let new_bit_pos = bit_pos_from_left_0_indexed % 5; + let shifted_chunk_u16 = (*byte as u16) << new_bit_pos; + let curr_u5_as_u8 = res_u5s[new_u5_idx].to_u8(); + res_u5s[new_u5_idx] = u5::try_from_u8(curr_u5_as_u8 | ((shifted_chunk_u16 & 0x001f) as u8)).unwrap(); + if new_u5_idx > 0 { + let curr_u5_as_u8 = res_u5s[new_u5_idx - 1].to_u8(); + res_u5s[new_u5_idx - 1] = u5::try_from_u8(curr_u5_as_u8 | (((shifted_chunk_u16 >> 5) & 0x001f) as u8)).unwrap(); + } + if new_u5_idx > 1 { + let curr_u5_as_u8 = res_u5s[new_u5_idx - 2].to_u8(); + res_u5s[new_u5_idx - 2] = u5::try_from_u8(curr_u5_as_u8 | (((shifted_chunk_u16 >> 10) & 0x001f) as u8)).unwrap(); + } + } + // Trim the highest feature bits. + while !res_u5s.is_empty() && res_u5s[0] == u5::try_from_u8(0).unwrap() { + res_u5s.remove(0); + } + writer.write(&res_u5s) + } +} + +impl Base32Len for Bolt11InvoiceFeatures { + fn base32_len(&self) -> usize { + self.to_base32().len() + } +} + +impl FromBase32 for Bolt11InvoiceFeatures { + type Err = bech32::Error; + + fn from_base32(field_data: &[u5]) -> Result { + // Explanation for the "7": the normal way to round up when dividing is to add the divisor + // minus one before dividing + let length_bytes = (field_data.len() * 5 + 7) / 8 as usize; + let mut res_bytes: Vec = vec![0; length_bytes]; + for (u5_idx, chunk) in field_data.iter().enumerate() { + let bit_pos_from_right_0_indexed = (field_data.len() - u5_idx - 1) * 5; + let new_byte_idx = (bit_pos_from_right_0_indexed / 8) as usize; + let new_bit_pos = bit_pos_from_right_0_indexed % 8; + let chunk_u16 = chunk.to_u8() as u16; + res_bytes[new_byte_idx] |= ((chunk_u16 << new_bit_pos) & 0xff) as u8; + if new_byte_idx != length_bytes - 1 { + res_bytes[new_byte_idx + 1] |= ((chunk_u16 >> (8-new_bit_pos)) & 0xff) as u8; + } + } + // Trim the highest feature bits. + while !res_bytes.is_empty() && res_bytes[res_bytes.len() - 1] == 0 { + res_bytes.pop(); + } + Ok(Bolt11InvoiceFeatures::from_le_bytes(res_bytes)) + } +} + +impl Features { + /// Create a blank Features with no features set + pub fn empty() -> Self { + Features { + flags: Vec::new(), + mark: PhantomData, + } + } + + /// Converts `Features` to `Features`. Only known `T` features relevant to context `C` are + /// included in the result. + fn to_context_internal(&self) -> Features { + let from_byte_count = T::KNOWN_FEATURE_MASK.len(); + let to_byte_count = C::KNOWN_FEATURE_MASK.len(); + let mut flags = Vec::new(); + for (i, byte) in self.flags.iter().enumerate() { + if i < from_byte_count && i < to_byte_count { + let from_known_features = T::KNOWN_FEATURE_MASK[i]; + let to_known_features = C::KNOWN_FEATURE_MASK[i]; + flags.push(byte & from_known_features & to_known_features); + } + } + Features:: { flags, mark: PhantomData, } + } + + /// Create a Features given a set of flags, in little-endian. This is in reverse byte order from + /// most on-the-wire encodings. + /// + /// This is not exported to bindings users as we don't support export across multiple T + pub fn from_le_bytes(flags: Vec) -> Features { + Features { + flags, + mark: PhantomData, + } + } + + /// Returns the feature set as a list of bytes, in little-endian. This is in reverse byte order + /// from most on-the-wire encodings. + pub fn le_flags(&self) -> &[u8] { + &self.flags + } + + /// Create a [`Features`] given a set of flags, in big-endian. This is in byte order from + /// most on-the-wire encodings. + /// + /// This is not exported to bindings users as we don't support export across multiple T + pub fn from_be_bytes(mut flags: Vec) -> Features { + flags.reverse(); // Swap to little-endian + Self { + flags, + mark: PhantomData, + } + } + + /// Returns true if this `Features` has any optional flags set + pub fn supports_any_optional_bits(&self) -> bool { + self.flags.iter().any(|&byte| (byte & ANY_OPTIONAL_FEATURES_MASK) != 0) + } + + /// Returns true if this `Features` object contains required features unknown by `other`. + pub fn requires_unknown_bits_from(&self, other: &Self) -> bool { + // Bitwise AND-ing with all even bits set except for known features will select required + // unknown features. + self.flags.iter().enumerate().any(|(i, &byte)| { + let unknown_features = unset_features_mask_at_position(other, i); + (byte & (ANY_REQUIRED_FEATURES_MASK & unknown_features)) != 0 + }) + } + + /// Returns the set of required features unknown by `other`, as their bit position. + pub fn required_unknown_bits_from(&self, other: &Self) -> Vec { + let mut unknown_bits = Vec::new(); + + // Bitwise AND-ing with all even bits set except for known features will select required + // unknown features. + self.flags.iter().enumerate().for_each(|(i, &byte)| { + let unknown_features = unset_features_mask_at_position(other, i); + if byte & unknown_features != 0 { + for bit in (0..8).step_by(2) { + if ((byte & unknown_features) >> bit) & 1 == 1 { + unknown_bits.push(i * 8 + bit); + } + } + } + }); + + unknown_bits + } + + /// Returns true if this `Features` object contains unknown feature flags which are set as + /// "required". + pub fn requires_unknown_bits(&self) -> bool { + // Bitwise AND-ing with all even bits set except for known features will select required + // unknown features. + let mut known_chunks = T::KNOWN_FEATURE_MASK.chunks(8); + for chunk in self.flags.chunks(8) { + let mut flag_bytes = [0; 8]; + flag_bytes[..chunk.len()].copy_from_slice(&chunk); + let flag_int = u64::from_le_bytes(flag_bytes); + + let known_chunk = known_chunks.next().unwrap_or(&[0; 0]); + let mut known_bytes = [0; 8]; + known_bytes[..known_chunk.len()].copy_from_slice(&known_chunk); + let known_int = u64::from_le_bytes(known_bytes); + + const REQ_MASK: u64 = u64::from_le_bytes([ANY_REQUIRED_FEATURES_MASK; 8]); + if flag_int & (REQ_MASK & !known_int) != 0 { + return true; + } + } + false + } + + /// Returns true if this `Features` supports any bits which we do not know of + pub fn supports_unknown_bits(&self) -> bool { + // Bitwise AND-ing with all even and odd bits set except for known features will select + // both required and optional unknown features. + let byte_count = T::KNOWN_FEATURE_MASK.len(); + self.flags.iter().enumerate().any(|(i, &byte)| { + let unknown_features = if i < byte_count { + !T::KNOWN_FEATURE_MASK[i] + } else { + 0b11_11_11_11 + }; + (byte & unknown_features) != 0 + }) + } + + /// Sets a required feature bit. Errors if `bit` is outside the feature range as defined + /// by [BOLT 9]. + /// + /// Note: Required bits are even. If an odd bit is given, then the corresponding even bit will + /// be set instead (i.e., `bit - 1`). + /// + /// [BOLT 9]: https://github.com/lightning/bolts/blob/master/09-features.md + pub fn set_required_feature_bit(&mut self, bit: usize) -> Result<(), ()> { + self.set_feature_bit(bit - (bit % 2)) + } + + /// Sets an optional feature bit. Errors if `bit` is outside the feature range as defined + /// by [BOLT 9]. + /// + /// Note: Optional bits are odd. If an even bit is given, then the corresponding odd bit will be + /// set instead (i.e., `bit + 1`). + /// + /// [BOLT 9]: https://github.com/lightning/bolts/blob/master/09-features.md + pub fn set_optional_feature_bit(&mut self, bit: usize) -> Result<(), ()> { + self.set_feature_bit(bit + (1 - (bit % 2))) + } + + fn set_feature_bit(&mut self, bit: usize) -> Result<(), ()> { + if bit > 255 { + return Err(()); + } + self.set_bit(bit, false) + } + + /// Sets a required custom feature bit. Errors if `bit` is outside the custom range as defined + /// by [bLIP 2] or if it is a known `T` feature. + /// + /// Note: Required bits are even. If an odd bit is given, then the corresponding even bit will + /// be set instead (i.e., `bit - 1`). + /// + /// [bLIP 2]: https://github.com/lightning/blips/blob/master/blip-0002.md#feature-bits + pub fn set_required_custom_bit(&mut self, bit: usize) -> Result<(), ()> { + self.set_custom_bit(bit - (bit % 2)) + } + + /// Sets an optional custom feature bit. Errors if `bit` is outside the custom range as defined + /// by [bLIP 2] or if it is a known `T` feature. + /// + /// Note: Optional bits are odd. If an even bit is given, then the corresponding odd bit will be + /// set instead (i.e., `bit + 1`). + /// + /// [bLIP 2]: https://github.com/lightning/blips/blob/master/blip-0002.md#feature-bits + pub fn set_optional_custom_bit(&mut self, bit: usize) -> Result<(), ()> { + self.set_custom_bit(bit + (1 - (bit % 2))) + } + + fn set_custom_bit(&mut self, bit: usize) -> Result<(), ()> { + if bit < 256 { + return Err(()); + } + self.set_bit(bit, true) + } + + fn set_bit(&mut self, bit: usize, custom: bool) -> Result<(), ()> { + let byte_offset = bit / 8; + let mask = 1 << (bit - 8 * byte_offset); + if byte_offset < T::KNOWN_FEATURE_MASK.len() && custom { + if (T::KNOWN_FEATURE_MASK[byte_offset] & mask) != 0 { + return Err(()); + } + } + + if self.flags.len() <= byte_offset { + self.flags.resize(byte_offset + 1, 0u8); + } + + self.flags[byte_offset] |= mask; + + Ok(()) + } +} + +impl Features { + /// Unsets the `upfront_shutdown_script` feature + pub fn clear_upfront_shutdown_script(mut self) -> Self { + ::clear_bits(&mut self.flags); + self + } +} + +impl Features { + /// Unsets the `shutdown_anysegwit` feature + pub fn clear_shutdown_anysegwit(mut self) -> Self { + ::clear_bits(&mut self.flags); + self + } +} + +impl Features { + /// Unsets the `wumbo` feature + pub fn clear_wumbo(mut self) -> Self { + ::clear_bits(&mut self.flags); + self + } +} + +impl Features { + /// Unsets the `scid_privacy` feature + pub fn clear_scid_privacy(&mut self) { + ::clear_bits(&mut self.flags); + } +} + +impl Features { + /// Unsets the `anchors_zero_fee_htlc_tx` feature + pub fn clear_anchors_zero_fee_htlc_tx(&mut self) { + ::clear_bits(&mut self.flags); + } +} + +impl Features { + /// Unsets the `route_blinding` feature + pub fn clear_route_blinding(&mut self) { + ::clear_bits(&mut self.flags); + } +} + +#[cfg(any(test, feature = "_test_utils"))] +impl Features { + /// Sets an unknown feature for testing + pub fn unknown() -> Self { + let mut features = Self::empty(); + features.set_unknown_feature_required(); + features + } +} + +pub(crate) fn unset_features_mask_at_position(other: &Features, index: usize) -> u8 { + if index < other.flags.len() { + // Form a mask similar to !T::KNOWN_FEATURE_MASK only for `other` + !(other.flags[index] + | ((other.flags[index] >> 1) & ANY_REQUIRED_FEATURES_MASK) + | ((other.flags[index] << 1) & ANY_OPTIONAL_FEATURES_MASK)) + } else { + 0b11_11_11_11 + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn sanity_test_unknown_bits() { + let features = ChannelFeatures::empty(); + assert!(!features.requires_unknown_bits()); + assert!(!features.supports_unknown_bits()); + + let mut features = ChannelFeatures::empty(); + features.set_unknown_feature_required(); + assert!(features.requires_unknown_bits()); + assert!(features.supports_unknown_bits()); + assert_eq!(features.required_unknown_bits_from(&ChannelFeatures::empty()), vec![123456788]); + + let mut features = ChannelFeatures::empty(); + features.set_unknown_feature_optional(); + assert!(!features.requires_unknown_bits()); + assert!(features.supports_unknown_bits()); + assert_eq!(features.required_unknown_bits_from(&ChannelFeatures::empty()), vec![]); + + let mut features = ChannelFeatures::empty(); + features.set_unknown_feature_required(); + features.set_custom_bit(123456786).unwrap(); + assert!(features.requires_unknown_bits()); + assert!(features.supports_unknown_bits()); + assert_eq!(features.required_unknown_bits_from(&ChannelFeatures::empty()), vec![123456786, 123456788]); + + let mut limiter = ChannelFeatures::empty(); + limiter.set_unknown_feature_optional(); + assert_eq!(features.required_unknown_bits_from(&limiter), vec![123456786]); + } + + #[test] + fn requires_unknown_bits_from() { + let mut features1 = InitFeatures::empty(); + let mut features2 = InitFeatures::empty(); + assert!(!features1.requires_unknown_bits_from(&features2)); + assert!(!features2.requires_unknown_bits_from(&features1)); + + features1.set_data_loss_protect_required(); + assert!(features1.requires_unknown_bits_from(&features2)); + assert!(!features2.requires_unknown_bits_from(&features1)); + + features2.set_data_loss_protect_optional(); + assert!(!features1.requires_unknown_bits_from(&features2)); + assert!(!features2.requires_unknown_bits_from(&features1)); + + features2.set_gossip_queries_required(); + assert!(!features1.requires_unknown_bits_from(&features2)); + assert!(features2.requires_unknown_bits_from(&features1)); + + features1.set_gossip_queries_optional(); + assert!(!features1.requires_unknown_bits_from(&features2)); + assert!(!features2.requires_unknown_bits_from(&features1)); + + features1.set_variable_length_onion_required(); + assert!(features1.requires_unknown_bits_from(&features2)); + assert!(!features2.requires_unknown_bits_from(&features1)); + + features2.set_variable_length_onion_optional(); + assert!(!features1.requires_unknown_bits_from(&features2)); + assert!(!features2.requires_unknown_bits_from(&features1)); + + features1.set_basic_mpp_required(); + features2.set_wumbo_required(); + assert!(features1.requires_unknown_bits_from(&features2)); + assert!(features2.requires_unknown_bits_from(&features1)); + } + + #[test] + fn convert_to_context_with_relevant_flags() { + let mut init_features = InitFeatures::empty(); + // Set a bunch of features we use, plus initial_routing_sync_required (which shouldn't get + // converted as it's only relevant in an init context). + init_features.set_initial_routing_sync_required(); + init_features.set_data_loss_protect_required(); + init_features.set_variable_length_onion_required(); + init_features.set_static_remote_key_required(); + init_features.set_payment_secret_required(); + init_features.set_basic_mpp_optional(); + init_features.set_wumbo_optional(); + init_features.set_anchors_zero_fee_htlc_tx_optional(); + init_features.set_route_blinding_optional(); + init_features.set_shutdown_any_segwit_optional(); + init_features.set_onion_messages_optional(); + init_features.set_channel_type_optional(); + init_features.set_scid_privacy_optional(); + init_features.set_zero_conf_optional(); + + assert!(init_features.initial_routing_sync()); + assert!(!init_features.supports_upfront_shutdown_script()); + assert!(!init_features.supports_gossip_queries()); + + let node_features: NodeFeatures = init_features.to_context(); + { + // Check that the flags are as expected: + // - option_data_loss_protect (req) + // - var_onion_optin (req) | static_remote_key (req) | payment_secret(req) + // - basic_mpp | wumbo | option_anchors_zero_fee_htlc_tx + // - option_route_blinding | opt_shutdown_anysegwit + // - onion_messages + // - option_channel_type | option_scid_alias + // - option_zeroconf + assert_eq!(node_features.flags.len(), 7); + assert_eq!(node_features.flags[0], 0b00000001); + assert_eq!(node_features.flags[1], 0b01010001); + assert_eq!(node_features.flags[2], 0b10001010); + assert_eq!(node_features.flags[3], 0b00001010); + assert_eq!(node_features.flags[4], 0b10000000); + assert_eq!(node_features.flags[5], 0b10100000); + assert_eq!(node_features.flags[6], 0b00001000); + } + + // Check that cleared flags are kept blank when converting back: + // - initial_routing_sync was not applicable to NodeContext + // - upfront_shutdown_script was cleared before converting + // - gossip_queries was cleared before converting + let features: InitFeatures = node_features.to_context_internal(); + assert!(!features.initial_routing_sync()); + assert!(!features.supports_upfront_shutdown_script()); + assert!(!init_features.supports_gossip_queries()); + } + + #[test] + fn convert_to_context_with_unknown_flags() { + // Ensure the `from` context has fewer known feature bytes than the `to` context. + assert!(::KNOWN_FEATURE_MASK.len() < + ::KNOWN_FEATURE_MASK.len()); + let mut channel_features = ChannelFeatures::empty(); + channel_features.set_unknown_feature_optional(); + assert!(channel_features.supports_unknown_bits()); + let invoice_features: Bolt11InvoiceFeatures = channel_features.to_context_internal(); + assert!(!invoice_features.supports_unknown_bits()); + } + + #[test] + fn set_feature_bits() { + let mut features = Bolt11InvoiceFeatures::empty(); + features.set_basic_mpp_optional(); + features.set_payment_secret_required(); + assert!(features.supports_basic_mpp()); + assert!(!features.requires_basic_mpp()); + assert!(features.requires_payment_secret()); + assert!(features.supports_payment_secret()); + + // Set flags manually + let mut features = NodeFeatures::empty(); + assert!(features.set_optional_feature_bit(55).is_ok()); + assert!(features.supports_keysend()); + assert!(features.set_optional_feature_bit(255).is_ok()); + assert!(features.set_required_feature_bit(256).is_err()); + } + + #[test] + fn set_custom_bits() { + let mut features = Bolt11InvoiceFeatures::empty(); + features.set_variable_length_onion_optional(); + assert_eq!(features.flags[1], 0b00000010); + + assert!(features.set_optional_custom_bit(255).is_err()); + assert!(features.set_required_custom_bit(256).is_ok()); + assert!(features.set_required_custom_bit(258).is_ok()); + assert_eq!(features.flags[31], 0b00000000); + assert_eq!(features.flags[32], 0b00000101); + + let known_bit = ::EVEN_BIT; + let byte_offset = ::BYTE_OFFSET; + assert_eq!(byte_offset, 1); + assert_eq!(features.flags[byte_offset], 0b00000010); + assert!(features.set_required_custom_bit(known_bit).is_err()); + assert_eq!(features.flags[byte_offset], 0b00000010); + + let mut features = Bolt11InvoiceFeatures::empty(); + assert!(features.set_optional_custom_bit(256).is_ok()); + assert!(features.set_optional_custom_bit(259).is_ok()); + assert_eq!(features.flags[32], 0b00001010); + + let mut features = Bolt11InvoiceFeatures::empty(); + assert!(features.set_required_custom_bit(257).is_ok()); + assert!(features.set_required_custom_bit(258).is_ok()); + assert_eq!(features.flags[32], 0b00000101); + } + + #[test] + fn invoice_features_encoding() { + let features_as_u5s = vec![ + u5::try_from_u8(6).unwrap(), + u5::try_from_u8(10).unwrap(), + u5::try_from_u8(25).unwrap(), + u5::try_from_u8(1).unwrap(), + u5::try_from_u8(10).unwrap(), + u5::try_from_u8(0).unwrap(), + u5::try_from_u8(20).unwrap(), + u5::try_from_u8(2).unwrap(), + u5::try_from_u8(0).unwrap(), + u5::try_from_u8(6).unwrap(), + u5::try_from_u8(0).unwrap(), + u5::try_from_u8(16).unwrap(), + u5::try_from_u8(1).unwrap(), + ]; + let features = Bolt11InvoiceFeatures::from_le_bytes(vec![1, 2, 3, 4, 5, 42, 100, 101]); + + // Test length calculation. + assert_eq!(features.base32_len(), 13); + + // Test serialization. + let features_serialized = features.to_base32(); + assert_eq!(features_as_u5s, features_serialized); + + // Test deserialization. + let features_deserialized = Bolt11InvoiceFeatures::from_base32(&features_as_u5s).unwrap(); + assert_eq!(features, features_deserialized); + } + + #[test] + fn test_channel_type_mapping() { + // If we map an Bolt11InvoiceFeatures with StaticRemoteKey optional, it should map into a + // required-StaticRemoteKey ChannelTypeFeatures. + let mut init_features = InitFeatures::empty(); + init_features.set_static_remote_key_optional(); + let converted_features = ChannelTypeFeatures::from_init(&init_features); + assert_eq!(converted_features, ChannelTypeFeatures::only_static_remote_key()); + assert!(!converted_features.supports_any_optional_bits()); + assert!(converted_features.requires_static_remote_key()); + } + + #[test] + fn test_excess_zero_bytes_ignored() { + // Checks that `Hash` and `PartialEq` ignore excess zero bytes, which may appear due to + // feature conversion or because a peer serialized their feature poorly. + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + + let mut zerod_features = InitFeatures::empty(); + zerod_features.flags = vec![0]; + let empty_features = InitFeatures::empty(); + assert!(empty_features.flags.is_empty()); + + assert_eq!(zerod_features, empty_features); + + let mut zerod_hash = DefaultHasher::new(); + zerod_features.hash(&mut zerod_hash); + let mut empty_hash = DefaultHasher::new(); + empty_features.hash(&mut empty_hash); + assert_eq!(zerod_hash.finish(), empty_hash.finish()); + } +} diff --git a/lightning-types/src/lib.rs b/lightning-types/src/lib.rs index aa21ec7cd59..bcfb2b62d43 100644 --- a/lightning-types/src/lib.rs +++ b/lightning-types/src/lib.rs @@ -23,5 +23,6 @@ extern crate alloc; extern crate core; +pub mod features; pub mod payment; pub mod routing; diff --git a/lightning/Cargo.toml b/lightning/Cargo.toml index 389ab087cc5..b55b675b065 100644 --- a/lightning/Cargo.toml +++ b/lightning/Cargo.toml @@ -17,7 +17,7 @@ rustdoc-args = ["--cfg", "docsrs"] [features] # Internal test utilities exposed to other repo crates -_test_utils = ["regex", "bitcoin/bitcoinconsensus"] +_test_utils = ["regex", "bitcoin/bitcoinconsensus", "lightning-types/_test_utils"] # Unlog messages superior at targeted level. max_level_off = [] max_level_error = [] @@ -55,6 +55,7 @@ libm = { version = "0.2", optional = true, default-features = false } [dev-dependencies] regex = "1.5.6" +lightning-types = { version = "0.1", path = "../lightning-types", features = ["_test_utils"] } [dev-dependencies.bitcoin] version = "0.31.2" diff --git a/lightning/src/ln/features.rs b/lightning/src/ln/features.rs index 3b041cbc5c2..11e2b12dcdb 100644 --- a/lightning/src/ln/features.rs +++ b/lightning/src/ln/features.rs @@ -9,966 +9,37 @@ //! Feature flag definitions for the Lightning protocol according to [BOLT #9]. //! -//! Lightning nodes advertise a supported set of operation through feature flags. Features are -//! applicable for a specific context as indicated in some [messages]. [`Features`] encapsulates -//! behavior for specifying and checking feature flags for a particular context. Each feature is -//! defined internally by a trait specifying the corresponding flags (i.e., even and odd bits). -//! -//! Whether a feature is considered "known" or "unknown" is relative to the implementation, whereas -//! the term "supports" is used in reference to a particular set of [`Features`]. That is, a node -//! supports a feature if it advertises the feature (as either required or optional) to its peers. -//! And the implementation can interpret a feature if the feature is known to it. -//! -//! The following features are currently required in the LDK: -//! - `VariableLengthOnion` - requires/supports variable-length routing onion payloads -//! (see [BOLT-4](https://github.com/lightning/bolts/blob/master/04-onion-routing.md) for more information). -//! - `StaticRemoteKey` - requires/supports static key for remote output -//! (see [BOLT-3](https://github.com/lightning/bolts/blob/master/03-transactions.md) for more information). -//! -//! The following features are currently supported in the LDK: -//! - `DataLossProtect` - requires/supports that a node which has somehow fallen behind, e.g., has been restored from an old backup, -//! can detect that it has fallen behind -//! (see [BOLT-2](https://github.com/lightning/bolts/blob/master/02-peer-protocol.md) for more information). -//! - `InitialRoutingSync` - requires/supports that the sending node needs a complete routing information dump -//! (see [BOLT-7](https://github.com/lightning/bolts/blob/master/07-routing-gossip.md#initial-sync) for more information). -//! - `UpfrontShutdownScript` - commits to a shutdown scriptpubkey when opening a channel -//! (see [BOLT-2](https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#the-open_channel-message) for more information). -//! - `GossipQueries` - requires/supports more sophisticated gossip control -//! (see [BOLT-7](https://github.com/lightning/bolts/blob/master/07-routing-gossip.md) for more information). -//! - `PaymentSecret` - requires/supports that a node supports payment_secret field -//! (see [BOLT-4](https://github.com/lightning/bolts/blob/master/04-onion-routing.md) for more information). -//! - `BasicMPP` - requires/supports that a node can receive basic multi-part payments -//! (see [BOLT-4](https://github.com/lightning/bolts/blob/master/04-onion-routing.md#basic-multi-part-payments) for more information). -//! - `Wumbo` - requires/supports that a node create large channels. Called `option_support_large_channel` in the spec. -//! (see [BOLT-2](https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#the-open_channel-message) for more information). -//! - `AnchorsZeroFeeHtlcTx` - requires/supports that commitment transactions include anchor outputs -//! and HTLC transactions are pre-signed with zero fee (see -//! [BOLT-3](https://github.com/lightning/bolts/blob/master/03-transactions.md) for more -//! information). -//! - `RouteBlinding` - requires/supports that a node can relay payments over blinded paths -//! (see [BOLT-4](https://github.com/lightning/bolts/blob/master/04-onion-routing.md#route-blinding) for more information). -//! - `ShutdownAnySegwit` - requires/supports that future segwit versions are allowed in `shutdown` -//! (see [BOLT-2](https://github.com/lightning/bolts/blob/master/02-peer-protocol.md) for more information). -//! - `OnionMessages` - requires/supports forwarding onion messages -//! (see [BOLT-7](https://github.com/lightning/bolts/pull/759/files) for more information). -// TODO: update link -//! - `ChannelType` - node supports the channel_type field in open/accept -//! (see [BOLT-2](https://github.com/lightning/bolts/blob/master/02-peer-protocol.md) for more information). -//! - `SCIDPrivacy` - supply channel aliases for routing -//! (see [BOLT-2](https://github.com/lightning/bolts/blob/master/02-peer-protocol.md) for more information). -//! - `PaymentMetadata` - include additional data in invoices which is passed to recipients in the -//! onion. -//! (see [BOLT-11](https://github.com/lightning/bolts/blob/master/11-payment-encoding.md) for -//! more). -//! - `ZeroConf` - supports accepting HTLCs and using channels prior to funding confirmation -//! (see -//! [BOLT-2](https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#the-channel_ready-message) -//! for more info). -//! - `Keysend` - send funds to a node without an invoice -//! (see the [`Keysend` feature assignment proposal](https://github.com/lightning/bolts/issues/605#issuecomment-606679798) for more information). -//! - `Trampoline` - supports receiving and forwarding Trampoline payments -//! (see the [`Trampoline` feature proposal](https://github.com/lightning/bolts/pull/836) for more information). -//! -//! LDK knows about the following features, but does not support them: -//! - `AnchorsNonzeroFeeHtlcTx` - the initial version of anchor outputs, which was later found to be -//! vulnerable (see this -//! [mailing list post](https://lists.linuxfoundation.org/pipermail/lightning-dev/2020-September/002796.html) -//! for more information). +//! See [`lightning_types::features`] for the list of features currently supported. //! //! [BOLT #9]: https://github.com/lightning/bolts/blob/master/09-features.md -//! [messages]: crate::ln::msgs + +pub use lightning_types::features::Features; +pub use lightning_types::features::{InitFeatures, NodeFeatures, ChannelFeatures}; +pub use lightning_types::features::{Bolt11InvoiceFeatures, OfferFeatures, InvoiceRequestFeatures}; +pub use lightning_types::features::{Bolt12InvoiceFeatures, BlindedHopFeatures}; +pub use lightning_types::features::ChannelTypeFeatures; #[allow(unused_imports)] use crate::prelude::*; use crate::{io, io_extras}; -use core::{cmp, fmt}; -use core::borrow::Borrow; -use core::hash::{Hash, Hasher}; -use core::marker::PhantomData; - -use bech32::{Base32Len, FromBase32, ToBase32, u5, WriteBase32}; use crate::ln::msgs::DecodeError; -use crate::util::ser::{Readable, WithoutLength, Writeable, Writer}; - -mod sealed { - #[allow(unused_imports)] - use crate::prelude::*; - use crate::ln::features::Features; - - /// The context in which [`Features`] are applicable. Defines which features are known to the - /// implementation, though specification of them as required or optional is up to the code - /// constructing a features object. - pub trait Context { - /// Bitmask for selecting features that are known to the implementation. - const KNOWN_FEATURE_MASK: &'static [u8]; - } - - /// Defines a [`Context`] by stating which features it requires and which are optional. Features - /// are specified as a comma-separated list of bytes where each byte is a pipe-delimited list of - /// feature identifiers. - macro_rules! define_context { - ($context: ident, [$( $( $known_feature: ident )|*, )*]) => { - #[derive(Eq, PartialEq)] - pub struct $context {} - - impl Context for $context { - const KNOWN_FEATURE_MASK: &'static [u8] = &[ - $( - 0b00_00_00_00 $(| - ::REQUIRED_MASK | - ::OPTIONAL_MASK)*, - )* - ]; - } - - impl alloc::fmt::Display for Features<$context> { - fn fmt(&self, fmt: &mut alloc::fmt::Formatter) -> Result<(), alloc::fmt::Error> { - $( - $( - fmt.write_fmt(format_args!("{}: {}, ", stringify!($known_feature), - if <$context as $known_feature>::requires_feature(&self.flags) { "required" } - else if <$context as $known_feature>::supports_feature(&self.flags) { "supported" } - else { "not supported" }))?; - )* - {} // Rust gets mad if we only have a $()* block here, so add a dummy {} - )* - fmt.write_fmt(format_args!("unknown flags: {}", - if self.requires_unknown_bits() { "required" } - else if self.supports_unknown_bits() { "supported" } else { "none" })) - } - } - }; - } - - define_context!(InitContext, [ - // Byte 0 - DataLossProtect | InitialRoutingSync | UpfrontShutdownScript | GossipQueries, - // Byte 1 - VariableLengthOnion | StaticRemoteKey | PaymentSecret, - // Byte 2 - BasicMPP | Wumbo | AnchorsNonzeroFeeHtlcTx | AnchorsZeroFeeHtlcTx, - // Byte 3 - RouteBlinding | ShutdownAnySegwit | Taproot, - // Byte 4 - OnionMessages, - // Byte 5 - ChannelType | SCIDPrivacy, - // Byte 6 - ZeroConf, - // Byte 7 - Trampoline, - ]); - define_context!(NodeContext, [ - // Byte 0 - DataLossProtect | UpfrontShutdownScript | GossipQueries, - // Byte 1 - VariableLengthOnion | StaticRemoteKey | PaymentSecret, - // Byte 2 - BasicMPP | Wumbo | AnchorsNonzeroFeeHtlcTx | AnchorsZeroFeeHtlcTx, - // Byte 3 - RouteBlinding | ShutdownAnySegwit | Taproot, - // Byte 4 - OnionMessages, - // Byte 5 - ChannelType | SCIDPrivacy, - // Byte 6 - ZeroConf | Keysend, - // Byte 7 - Trampoline, - ]); - define_context!(ChannelContext, []); - define_context!(Bolt11InvoiceContext, [ - // Byte 0 - , - // Byte 1 - VariableLengthOnion | PaymentSecret, - // Byte 2 - BasicMPP, - // Byte 3 - , - // Byte 4 - , - // Byte 5 - , - // Byte 6 - PaymentMetadata, - // Byte 7 - Trampoline, - ]); - define_context!(OfferContext, []); - define_context!(InvoiceRequestContext, []); - define_context!(Bolt12InvoiceContext, [ - // Byte 0 - , - // Byte 1 - , - // Byte 2 - BasicMPP, - ]); - define_context!(BlindedHopContext, []); - // This isn't a "real" feature context, and is only used in the channel_type field in an - // `OpenChannel` message. - define_context!(ChannelTypeContext, [ - // Byte 0 - , - // Byte 1 - StaticRemoteKey, - // Byte 2 - AnchorsNonzeroFeeHtlcTx | AnchorsZeroFeeHtlcTx, - // Byte 3 - Taproot, - // Byte 4 - , - // Byte 5 - SCIDPrivacy, - // Byte 6 - ZeroConf, - ]); - - /// Defines a feature with the given bits for the specified [`Context`]s. The generated trait is - /// useful for manipulating feature flags. - macro_rules! define_feature { - ($odd_bit: expr, $feature: ident, [$($context: ty),+], $doc: expr, $optional_setter: ident, - $required_setter: ident, $supported_getter: ident) => { - #[doc = $doc] - /// - /// See [BOLT #9] for details. - /// - /// [BOLT #9]: https://github.com/lightning/bolts/blob/master/09-features.md - pub trait $feature: Context { - /// The bit used to signify that the feature is required. - const EVEN_BIT: usize = $odd_bit - 1; - - /// The bit used to signify that the feature is optional. - const ODD_BIT: usize = $odd_bit; - - /// Assertion that [`EVEN_BIT`] is actually even. - /// - /// [`EVEN_BIT`]: #associatedconstant.EVEN_BIT - const ASSERT_EVEN_BIT_PARITY: usize; - - /// Assertion that [`ODD_BIT`] is actually odd. - /// - /// [`ODD_BIT`]: #associatedconstant.ODD_BIT - const ASSERT_ODD_BIT_PARITY: usize; - - /// Assertion that the bits are set in the context's [`KNOWN_FEATURE_MASK`]. - /// - /// [`KNOWN_FEATURE_MASK`]: Context::KNOWN_FEATURE_MASK - #[cfg(not(test))] // We violate this constraint with `UnknownFeature` - const ASSERT_BITS_IN_MASK: u8; - - /// The byte where the feature is set. - const BYTE_OFFSET: usize = Self::EVEN_BIT / 8; - - /// The bitmask for the feature's required flag relative to the [`BYTE_OFFSET`]. - /// - /// [`BYTE_OFFSET`]: #associatedconstant.BYTE_OFFSET - const REQUIRED_MASK: u8 = 1 << (Self::EVEN_BIT - 8 * Self::BYTE_OFFSET); - - /// The bitmask for the feature's optional flag relative to the [`BYTE_OFFSET`]. - /// - /// [`BYTE_OFFSET`]: #associatedconstant.BYTE_OFFSET - const OPTIONAL_MASK: u8 = 1 << (Self::ODD_BIT - 8 * Self::BYTE_OFFSET); - - /// Returns whether the feature is required by the given flags. - #[inline] - fn requires_feature(flags: &Vec) -> bool { - flags.len() > Self::BYTE_OFFSET && - (flags[Self::BYTE_OFFSET] & Self::REQUIRED_MASK) != 0 - } - - /// Returns whether the feature is supported by the given flags. - #[inline] - fn supports_feature(flags: &Vec) -> bool { - flags.len() > Self::BYTE_OFFSET && - (flags[Self::BYTE_OFFSET] & (Self::REQUIRED_MASK | Self::OPTIONAL_MASK)) != 0 - } - - /// Sets the feature's required (even) bit in the given flags. - #[inline] - fn set_required_bit(flags: &mut Vec) { - if flags.len() <= Self::BYTE_OFFSET { - flags.resize(Self::BYTE_OFFSET + 1, 0u8); - } - - flags[Self::BYTE_OFFSET] |= Self::REQUIRED_MASK; - flags[Self::BYTE_OFFSET] &= !Self::OPTIONAL_MASK; - } - - /// Sets the feature's optional (odd) bit in the given flags. - #[inline] - fn set_optional_bit(flags: &mut Vec) { - if flags.len() <= Self::BYTE_OFFSET { - flags.resize(Self::BYTE_OFFSET + 1, 0u8); - } - - flags[Self::BYTE_OFFSET] |= Self::OPTIONAL_MASK; - } - - /// Clears the feature's required (even) and optional (odd) bits from the given - /// flags. - #[inline] - fn clear_bits(flags: &mut Vec) { - if flags.len() > Self::BYTE_OFFSET { - flags[Self::BYTE_OFFSET] &= !Self::REQUIRED_MASK; - flags[Self::BYTE_OFFSET] &= !Self::OPTIONAL_MASK; - } - - let last_non_zero_byte = flags.iter().rposition(|&byte| byte != 0); - let size = if let Some(offset) = last_non_zero_byte { offset + 1 } else { 0 }; - flags.resize(size, 0u8); - } - } - - impl Features { - /// Set this feature as optional. - pub fn $optional_setter(&mut self) { - ::set_optional_bit(&mut self.flags); - } - - /// Set this feature as required. - pub fn $required_setter(&mut self) { - ::set_required_bit(&mut self.flags); - } - - /// Checks if this feature is supported. - pub fn $supported_getter(&self) -> bool { - ::supports_feature(&self.flags) - } - } - - $( - impl $feature for $context { - // EVEN_BIT % 2 == 0 - const ASSERT_EVEN_BIT_PARITY: usize = 0 - (::EVEN_BIT % 2); - - // ODD_BIT % 2 == 1 - const ASSERT_ODD_BIT_PARITY: usize = (::ODD_BIT % 2) - 1; - - // (byte & (REQUIRED_MASK | OPTIONAL_MASK)) >> (EVEN_BIT % 8) == 3 - #[cfg(not(test))] // We violate this constraint with `UnknownFeature` - const ASSERT_BITS_IN_MASK: u8 = - ((<$context>::KNOWN_FEATURE_MASK[::BYTE_OFFSET] & (::REQUIRED_MASK | ::OPTIONAL_MASK)) - >> (::EVEN_BIT % 8)) - 3; - } - )* - }; - ($odd_bit: expr, $feature: ident, [$($context: ty),+], $doc: expr, $optional_setter: ident, - $required_setter: ident, $supported_getter: ident, $required_getter: ident) => { - define_feature!($odd_bit, $feature, [$($context),+], $doc, $optional_setter, $required_setter, $supported_getter); - impl Features { - /// Checks if this feature is required. - pub fn $required_getter(&self) -> bool { - ::requires_feature(&self.flags) - } - } - } - } - - define_feature!(1, DataLossProtect, [InitContext, NodeContext], - "Feature flags for `option_data_loss_protect`.", set_data_loss_protect_optional, - set_data_loss_protect_required, supports_data_loss_protect, requires_data_loss_protect); - // NOTE: Per Bolt #9, initial_routing_sync has no even bit. - define_feature!(3, InitialRoutingSync, [InitContext], "Feature flags for `initial_routing_sync`.", - set_initial_routing_sync_optional, set_initial_routing_sync_required, - initial_routing_sync); - define_feature!(5, UpfrontShutdownScript, [InitContext, NodeContext], - "Feature flags for `option_upfront_shutdown_script`.", set_upfront_shutdown_script_optional, - set_upfront_shutdown_script_required, supports_upfront_shutdown_script, - requires_upfront_shutdown_script); - define_feature!(7, GossipQueries, [InitContext, NodeContext], - "Feature flags for `gossip_queries`.", set_gossip_queries_optional, set_gossip_queries_required, - supports_gossip_queries, requires_gossip_queries); - define_feature!(9, VariableLengthOnion, [InitContext, NodeContext, Bolt11InvoiceContext], - "Feature flags for `var_onion_optin`.", set_variable_length_onion_optional, - set_variable_length_onion_required, supports_variable_length_onion, - requires_variable_length_onion); - define_feature!(13, StaticRemoteKey, [InitContext, NodeContext, ChannelTypeContext], - "Feature flags for `option_static_remotekey`.", set_static_remote_key_optional, - set_static_remote_key_required, supports_static_remote_key, requires_static_remote_key); - define_feature!(15, PaymentSecret, [InitContext, NodeContext, Bolt11InvoiceContext], - "Feature flags for `payment_secret`.", set_payment_secret_optional, set_payment_secret_required, - supports_payment_secret, requires_payment_secret); - define_feature!(17, BasicMPP, [InitContext, NodeContext, Bolt11InvoiceContext, Bolt12InvoiceContext], - "Feature flags for `basic_mpp`.", set_basic_mpp_optional, set_basic_mpp_required, - supports_basic_mpp, requires_basic_mpp); - define_feature!(19, Wumbo, [InitContext, NodeContext], - "Feature flags for `option_support_large_channel` (aka wumbo channels).", set_wumbo_optional, set_wumbo_required, - supports_wumbo, requires_wumbo); - define_feature!(21, AnchorsNonzeroFeeHtlcTx, [InitContext, NodeContext, ChannelTypeContext], - "Feature flags for `option_anchors_nonzero_fee_htlc_tx`.", set_anchors_nonzero_fee_htlc_tx_optional, - set_anchors_nonzero_fee_htlc_tx_required, supports_anchors_nonzero_fee_htlc_tx, requires_anchors_nonzero_fee_htlc_tx); - define_feature!(23, AnchorsZeroFeeHtlcTx, [InitContext, NodeContext, ChannelTypeContext], - "Feature flags for `option_anchors_zero_fee_htlc_tx`.", set_anchors_zero_fee_htlc_tx_optional, - set_anchors_zero_fee_htlc_tx_required, supports_anchors_zero_fee_htlc_tx, requires_anchors_zero_fee_htlc_tx); - define_feature!(25, RouteBlinding, [InitContext, NodeContext], - "Feature flags for `option_route_blinding`.", set_route_blinding_optional, - set_route_blinding_required, supports_route_blinding, requires_route_blinding); - define_feature!(27, ShutdownAnySegwit, [InitContext, NodeContext], - "Feature flags for `opt_shutdown_anysegwit`.", set_shutdown_any_segwit_optional, - set_shutdown_any_segwit_required, supports_shutdown_anysegwit, requires_shutdown_anysegwit); - define_feature!(31, Taproot, [InitContext, NodeContext, ChannelTypeContext], - "Feature flags for `option_taproot`.", set_taproot_optional, - set_taproot_required, supports_taproot, requires_taproot); - define_feature!(39, OnionMessages, [InitContext, NodeContext], - "Feature flags for `option_onion_messages`.", set_onion_messages_optional, - set_onion_messages_required, supports_onion_messages, requires_onion_messages); - define_feature!(45, ChannelType, [InitContext, NodeContext], - "Feature flags for `option_channel_type`.", set_channel_type_optional, - set_channel_type_required, supports_channel_type, requires_channel_type); - define_feature!(47, SCIDPrivacy, [InitContext, NodeContext, ChannelTypeContext], - "Feature flags for only forwarding with SCID aliasing. Called `option_scid_alias` in the BOLTs", - set_scid_privacy_optional, set_scid_privacy_required, supports_scid_privacy, requires_scid_privacy); - define_feature!(49, PaymentMetadata, [Bolt11InvoiceContext], - "Feature flags for payment metadata in invoices.", set_payment_metadata_optional, - set_payment_metadata_required, supports_payment_metadata, requires_payment_metadata); - define_feature!(51, ZeroConf, [InitContext, NodeContext, ChannelTypeContext], - "Feature flags for accepting channels with zero confirmations. Called `option_zeroconf` in the BOLTs", - set_zero_conf_optional, set_zero_conf_required, supports_zero_conf, requires_zero_conf); - define_feature!(55, Keysend, [NodeContext], - "Feature flags for keysend payments.", set_keysend_optional, set_keysend_required, - supports_keysend, requires_keysend); - define_feature!(57, Trampoline, [InitContext, NodeContext, Bolt11InvoiceContext], - "Feature flags for Trampoline routing.", set_trampoline_routing_optional, set_trampoline_routing_required, - supports_trampoline_routing, requires_trampoline_routing); - // Note: update the module-level docs when a new feature bit is added! - - #[cfg(test)] - define_feature!(123456789, UnknownFeature, - [NodeContext, ChannelContext, Bolt11InvoiceContext, OfferContext, InvoiceRequestContext, Bolt12InvoiceContext, BlindedHopContext], - "Feature flags for an unknown feature used in testing.", set_unknown_feature_optional, - set_unknown_feature_required, supports_unknown_test_feature, requires_unknown_test_feature); -} - -const ANY_REQUIRED_FEATURES_MASK: u8 = 0b01_01_01_01; -const ANY_OPTIONAL_FEATURES_MASK: u8 = 0b10_10_10_10; - -/// Tracks the set of features which a node implements, templated by the context in which it -/// appears. -/// -/// This is not exported to bindings users as we map the concrete feature types below directly instead -#[derive(Eq)] -pub struct Features { - /// Note that, for convenience, flags is LITTLE endian (despite being big-endian on the wire) - flags: Vec, - mark: PhantomData, -} - -impl> core::ops::BitOrAssign for Features { - fn bitor_assign(&mut self, rhs: Rhs) { - let total_feature_len = cmp::max(self.flags.len(), rhs.borrow().flags.len()); - self.flags.resize(total_feature_len, 0u8); - for (byte, rhs_byte) in self.flags.iter_mut().zip(rhs.borrow().flags.iter()) { - *byte |= *rhs_byte; - } - } -} - -impl core::ops::BitOr for Features { - type Output = Self; - - fn bitor(mut self, o: Self) -> Self { - self |= o; - self - } -} - -impl Clone for Features { - fn clone(&self) -> Self { - Self { - flags: self.flags.clone(), - mark: PhantomData, - } - } -} -impl Hash for Features { - fn hash(&self, hasher: &mut H) { - let mut nonzero_flags = &self.flags[..]; - while nonzero_flags.last() == Some(&0) { - nonzero_flags = &nonzero_flags[..nonzero_flags.len() - 1]; - } - nonzero_flags.hash(hasher); - } -} -impl PartialEq for Features { - fn eq(&self, o: &Self) -> bool { - let mut o_iter = o.flags.iter(); - let mut self_iter = self.flags.iter(); - loop { - match (o_iter.next(), self_iter.next()) { - (Some(o), Some(us)) => if o != us { return false }, - (Some(b), None) | (None, Some(b)) => if *b != 0 { return false }, - (None, None) => return true, - } - } - } -} -impl PartialOrd for Features { - fn partial_cmp(&self, other: &Self) -> Option { - self.flags.partial_cmp(&other.flags) - } -} -impl Ord for Features { - fn cmp(&self, other: &Self) -> cmp::Ordering { - self.flags.cmp(&other.flags) - } -} -impl fmt::Debug for Features { - fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { - self.flags.fmt(fmt) - } -} - -/// Features used within an `init` message. -pub type InitFeatures = Features; -/// Features used within a `node_announcement` message. -pub type NodeFeatures = Features; -/// Features used within a `channel_announcement` message. -pub type ChannelFeatures = Features; -/// Features used within an invoice. -pub type Bolt11InvoiceFeatures = Features; -/// Features used within an `offer`. -pub type OfferFeatures = Features; -/// Features used within an `invoice_request`. -pub type InvoiceRequestFeatures = Features; -/// Features used within an `invoice`. -pub type Bolt12InvoiceFeatures = Features; -/// Features used within BOLT 4 encrypted_data_tlv and BOLT 12 blinded_payinfo -pub type BlindedHopFeatures = Features; - -/// Features used within the channel_type field in an OpenChannel message. -/// -/// A channel is always of some known "type", describing the transaction formats used and the exact -/// semantics of our interaction with our peer. -/// -/// Note that because a channel is a specific type which is proposed by the opener and accepted by -/// the counterparty, only required features are allowed here. -/// -/// This is serialized differently from other feature types - it is not prefixed by a length, and -/// thus must only appear inside a TLV where its length is known in advance. -pub type ChannelTypeFeatures = Features; - -impl InitFeatures { - /// Writes all features present up to, and including, 13. - pub(crate) fn write_up_to_13(&self, w: &mut W) -> Result<(), io::Error> { - let len = cmp::min(2, self.flags.len()); - (len as u16).write(w)?; - for i in (0..len).rev() { - if i == 0 { - self.flags[i].write(w)?; - } else { - // On byte 1, we want up-to-and-including-bit-13, 0-indexed, which is - // up-to-and-including-bit-5, 0-indexed, on this byte: - (self.flags[i] & 0b00_11_11_11).write(w)?; - } - } - Ok(()) - } - - /// Converts `InitFeatures` to `Features`. Only known `InitFeatures` relevant to context `C` - /// are included in the result. - pub(crate) fn to_context(&self) -> Features { - self.to_context_internal() - } -} - -impl Bolt11InvoiceFeatures { - /// Converts `Bolt11InvoiceFeatures` to `Features`. Only known `Bolt11InvoiceFeatures` relevant to - /// context `C` are included in the result. - pub(crate) fn to_context(&self) -> Features { - self.to_context_internal() - } - - /// Getting a route for a keysend payment to a private node requires providing the payee's - /// features (since they were not announced in a node announcement). However, keysend payments - /// don't have an invoice to pull the payee's features from, so this method is provided for use in - /// [`PaymentParameters::for_keysend`], thus omitting the need for payers to manually construct an - /// `Bolt11InvoiceFeatures` for [`find_route`]. - /// - /// MPP keysend is not widely supported yet, so we parameterize support to allow the user to - /// choose whether their router should find multi-part routes. - /// - /// [`PaymentParameters::for_keysend`]: crate::routing::router::PaymentParameters::for_keysend - /// [`find_route`]: crate::routing::router::find_route - pub(crate) fn for_keysend(allow_mpp: bool) -> Bolt11InvoiceFeatures { - let mut res = Bolt11InvoiceFeatures::empty(); - res.set_variable_length_onion_optional(); - if allow_mpp { - res.set_basic_mpp_optional(); - } - res - } -} - -impl Bolt12InvoiceFeatures { - /// Converts [`Bolt12InvoiceFeatures`] to [`Features`]. Only known [`Bolt12InvoiceFeatures`] - /// relevant to context `C` are included in the result. - pub(crate) fn to_context(&self) -> Features { - self.to_context_internal() - } -} - -impl ChannelTypeFeatures { - // Maps the relevant `InitFeatures` to `ChannelTypeFeatures`. Any unknown features to - // `ChannelTypeFeatures` are not included in the result. - pub(crate) fn from_init(init: &InitFeatures) -> Self { - let mut ret = init.to_context_internal(); - // ChannelTypeFeatures must only contain required bits, so we OR the required forms of all - // optional bits and then AND out the optional ones. - for byte in ret.flags.iter_mut() { - *byte |= (*byte & ANY_OPTIONAL_FEATURES_MASK) >> 1; - *byte &= ANY_REQUIRED_FEATURES_MASK; - } - ret - } - - /// Constructs a ChannelTypeFeatures with only static_remotekey set - pub(crate) fn only_static_remote_key() -> Self { - let mut ret = Self::empty(); - ::set_required_bit(&mut ret.flags); - ret - } - - /// Constructs a ChannelTypeFeatures with anchors support - pub(crate) fn anchors_zero_htlc_fee_and_dependencies() -> Self { - let mut ret = Self::empty(); - ::set_required_bit(&mut ret.flags); - ::set_required_bit(&mut ret.flags); - ret - } -} - -impl ToBase32 for Bolt11InvoiceFeatures { - fn write_base32(&self, writer: &mut W) -> Result<(), ::Err> { - // Explanation for the "4": the normal way to round up when dividing is to add the divisor - // minus one before dividing - let length_u5s = (self.flags.len() * 8 + 4) / 5 as usize; - let mut res_u5s: Vec = vec![u5::try_from_u8(0).unwrap(); length_u5s]; - for (byte_idx, byte) in self.flags.iter().enumerate() { - let bit_pos_from_left_0_indexed = byte_idx * 8; - let new_u5_idx = length_u5s - (bit_pos_from_left_0_indexed / 5) as usize - 1; - let new_bit_pos = bit_pos_from_left_0_indexed % 5; - let shifted_chunk_u16 = (*byte as u16) << new_bit_pos; - let curr_u5_as_u8 = res_u5s[new_u5_idx].to_u8(); - res_u5s[new_u5_idx] = u5::try_from_u8(curr_u5_as_u8 | ((shifted_chunk_u16 & 0x001f) as u8)).unwrap(); - if new_u5_idx > 0 { - let curr_u5_as_u8 = res_u5s[new_u5_idx - 1].to_u8(); - res_u5s[new_u5_idx - 1] = u5::try_from_u8(curr_u5_as_u8 | (((shifted_chunk_u16 >> 5) & 0x001f) as u8)).unwrap(); - } - if new_u5_idx > 1 { - let curr_u5_as_u8 = res_u5s[new_u5_idx - 2].to_u8(); - res_u5s[new_u5_idx - 2] = u5::try_from_u8(curr_u5_as_u8 | (((shifted_chunk_u16 >> 10) & 0x001f) as u8)).unwrap(); - } - } - // Trim the highest feature bits. - while !res_u5s.is_empty() && res_u5s[0] == u5::try_from_u8(0).unwrap() { - res_u5s.remove(0); - } - writer.write(&res_u5s) - } -} - -impl Base32Len for Bolt11InvoiceFeatures { - fn base32_len(&self) -> usize { - self.to_base32().len() - } -} - -impl FromBase32 for Bolt11InvoiceFeatures { - type Err = bech32::Error; - - fn from_base32(field_data: &[u5]) -> Result { - // Explanation for the "7": the normal way to round up when dividing is to add the divisor - // minus one before dividing - let length_bytes = (field_data.len() * 5 + 7) / 8 as usize; - let mut res_bytes: Vec = vec![0; length_bytes]; - for (u5_idx, chunk) in field_data.iter().enumerate() { - let bit_pos_from_right_0_indexed = (field_data.len() - u5_idx - 1) * 5; - let new_byte_idx = (bit_pos_from_right_0_indexed / 8) as usize; - let new_bit_pos = bit_pos_from_right_0_indexed % 8; - let chunk_u16 = chunk.to_u8() as u16; - res_bytes[new_byte_idx] |= ((chunk_u16 << new_bit_pos) & 0xff) as u8; - if new_byte_idx != length_bytes - 1 { - res_bytes[new_byte_idx + 1] |= ((chunk_u16 >> (8-new_bit_pos)) & 0xff) as u8; - } - } - // Trim the highest feature bits. - while !res_bytes.is_empty() && res_bytes[res_bytes.len() - 1] == 0 { - res_bytes.pop(); - } - Ok(Bolt11InvoiceFeatures::from_le_bytes(res_bytes)) - } -} - -impl Features { - /// Create a blank Features with no features set - pub fn empty() -> Self { - Features { - flags: Vec::new(), - mark: PhantomData, - } - } - - /// Converts `Features` to `Features`. Only known `T` features relevant to context `C` are - /// included in the result. - fn to_context_internal(&self) -> Features { - let from_byte_count = T::KNOWN_FEATURE_MASK.len(); - let to_byte_count = C::KNOWN_FEATURE_MASK.len(); - let mut flags = Vec::new(); - for (i, byte) in self.flags.iter().enumerate() { - if i < from_byte_count && i < to_byte_count { - let from_known_features = T::KNOWN_FEATURE_MASK[i]; - let to_known_features = C::KNOWN_FEATURE_MASK[i]; - flags.push(byte & from_known_features & to_known_features); - } - } - Features:: { flags, mark: PhantomData, } - } - - /// Create a Features given a set of flags, in little-endian. This is in reverse byte order from - /// most on-the-wire encodings. - /// - /// This is not exported to bindings users as we don't support export across multiple T - pub fn from_le_bytes(flags: Vec) -> Features { - Features { - flags, - mark: PhantomData, - } - } - - #[cfg(test)] - /// Gets the underlying flags set, in LE. - pub fn le_flags(&self) -> &Vec { - &self.flags - } +use crate::util::ser::{Writer, Readable, Writeable, WithoutLength}; - fn write_be(&self, w: &mut W) -> Result<(), io::Error> { - for f in self.flags.iter().rev() { // Swap back to big-endian - f.write(w)?; - } - Ok(()) - } - - /// Create a [`Features`] given a set of flags, in big-endian. This is in byte order from - /// most on-the-wire encodings. - /// - /// This is not exported to bindings users as we don't support export across multiple T - pub fn from_be_bytes(mut flags: Vec) -> Features { - flags.reverse(); // Swap to little-endian - Self { - flags, - mark: PhantomData, - } - } - - pub(crate) fn supports_any_optional_bits(&self) -> bool { - self.flags.iter().any(|&byte| (byte & ANY_OPTIONAL_FEATURES_MASK) != 0) - } - - /// Returns true if this `Features` object contains required features unknown by `other`. - pub fn requires_unknown_bits_from(&self, other: &Self) -> bool { - // Bitwise AND-ing with all even bits set except for known features will select required - // unknown features. - self.flags.iter().enumerate().any(|(i, &byte)| { - let unknown_features = unset_features_mask_at_position(other, i); - (byte & (ANY_REQUIRED_FEATURES_MASK & unknown_features)) != 0 - }) - } - - pub(crate) fn required_unknown_bits_from(&self, other: &Self) -> Vec { - let mut unknown_bits = Vec::new(); - - // Bitwise AND-ing with all even bits set except for known features will select required - // unknown features. - self.flags.iter().enumerate().for_each(|(i, &byte)| { - let unknown_features = unset_features_mask_at_position(other, i); - if byte & unknown_features != 0 { - for bit in (0..8).step_by(2) { - if ((byte & unknown_features) >> bit) & 1 == 1 { - unknown_bits.push(i * 8 + bit); - } - } - } - }); - - unknown_bits - } - - /// Returns true if this `Features` object contains unknown feature flags which are set as - /// "required". - pub fn requires_unknown_bits(&self) -> bool { - // Bitwise AND-ing with all even bits set except for known features will select required - // unknown features. - let mut known_chunks = T::KNOWN_FEATURE_MASK.chunks(8); - for chunk in self.flags.chunks(8) { - let mut flag_bytes = [0; 8]; - flag_bytes[..chunk.len()].copy_from_slice(&chunk); - let flag_int = u64::from_le_bytes(flag_bytes); - - let known_chunk = known_chunks.next().unwrap_or(&[0; 0]); - let mut known_bytes = [0; 8]; - known_bytes[..known_chunk.len()].copy_from_slice(&known_chunk); - let known_int = u64::from_le_bytes(known_bytes); - - const REQ_MASK: u64 = u64::from_le_bytes([ANY_REQUIRED_FEATURES_MASK; 8]); - if flag_int & (REQ_MASK & !known_int) != 0 { - return true; - } - } - false - } - - pub(crate) fn supports_unknown_bits(&self) -> bool { - // Bitwise AND-ing with all even and odd bits set except for known features will select - // both required and optional unknown features. - let byte_count = T::KNOWN_FEATURE_MASK.len(); - self.flags.iter().enumerate().any(|(i, &byte)| { - let unknown_features = if i < byte_count { - !T::KNOWN_FEATURE_MASK[i] - } else { - 0b11_11_11_11 - }; - (byte & unknown_features) != 0 - }) - } - - /// Sets a required feature bit. Errors if `bit` is outside the feature range as defined - /// by [BOLT 9]. - /// - /// Note: Required bits are even. If an odd bit is given, then the corresponding even bit will - /// be set instead (i.e., `bit - 1`). - /// - /// [BOLT 9]: https://github.com/lightning/bolts/blob/master/09-features.md - pub fn set_required_feature_bit(&mut self, bit: usize) -> Result<(), ()> { - self.set_feature_bit(bit - (bit % 2)) - } - - /// Sets an optional feature bit. Errors if `bit` is outside the feature range as defined - /// by [BOLT 9]. - /// - /// Note: Optional bits are odd. If an even bit is given, then the corresponding odd bit will be - /// set instead (i.e., `bit + 1`). - /// - /// [BOLT 9]: https://github.com/lightning/bolts/blob/master/09-features.md - pub fn set_optional_feature_bit(&mut self, bit: usize) -> Result<(), ()> { - self.set_feature_bit(bit + (1 - (bit % 2))) - } - - fn set_feature_bit(&mut self, bit: usize) -> Result<(), ()> { - if bit > 255 { - return Err(()); - } - self.set_bit(bit, false) - } - - /// Sets a required custom feature bit. Errors if `bit` is outside the custom range as defined - /// by [bLIP 2] or if it is a known `T` feature. - /// - /// Note: Required bits are even. If an odd bit is given, then the corresponding even bit will - /// be set instead (i.e., `bit - 1`). - /// - /// [bLIP 2]: https://github.com/lightning/blips/blob/master/blip-0002.md#feature-bits - pub fn set_required_custom_bit(&mut self, bit: usize) -> Result<(), ()> { - self.set_custom_bit(bit - (bit % 2)) - } - - /// Sets an optional custom feature bit. Errors if `bit` is outside the custom range as defined - /// by [bLIP 2] or if it is a known `T` feature. - /// - /// Note: Optional bits are odd. If an even bit is given, then the corresponding odd bit will be - /// set instead (i.e., `bit + 1`). - /// - /// [bLIP 2]: https://github.com/lightning/blips/blob/master/blip-0002.md#feature-bits - pub fn set_optional_custom_bit(&mut self, bit: usize) -> Result<(), ()> { - self.set_custom_bit(bit + (1 - (bit % 2))) - } - - fn set_custom_bit(&mut self, bit: usize) -> Result<(), ()> { - if bit < 256 { - return Err(()); - } - self.set_bit(bit, true) - } - - fn set_bit(&mut self, bit: usize, custom: bool) -> Result<(), ()> { - let byte_offset = bit / 8; - let mask = 1 << (bit - 8 * byte_offset); - if byte_offset < T::KNOWN_FEATURE_MASK.len() && custom { - if (T::KNOWN_FEATURE_MASK[byte_offset] & mask) != 0 { - return Err(()); - } - } - - if self.flags.len() <= byte_offset { - self.flags.resize(byte_offset + 1, 0u8); - } - - self.flags[byte_offset] |= mask; - - Ok(()) - } -} - -impl Features { - #[cfg(test)] - pub(crate) fn clear_upfront_shutdown_script(mut self) -> Self { - ::clear_bits(&mut self.flags); - self - } -} - -impl Features { - #[cfg(test)] - pub(crate) fn clear_shutdown_anysegwit(mut self) -> Self { - ::clear_bits(&mut self.flags); - self - } -} - -impl Features { - #[cfg(test)] - pub(crate) fn clear_wumbo(mut self) -> Self { - ::clear_bits(&mut self.flags); - self - } -} - -impl Features { - pub(crate) fn clear_scid_privacy(&mut self) { - ::clear_bits(&mut self.flags); - } -} - -impl Features { - pub(crate) fn clear_anchors_zero_fee_htlc_tx(&mut self) { - ::clear_bits(&mut self.flags); - } -} - -impl Features { - #[cfg(test)] - pub(crate) fn clear_route_blinding(&mut self) { - ::clear_bits(&mut self.flags); - } -} - -#[cfg(test)] -impl Features { - pub(crate) fn unknown() -> Self { - let mut features = Self::empty(); - features.set_unknown_feature_required(); - features +fn write_be(w: &mut W, le_flags: &[u8]) -> Result<(), io::Error> { + for f in le_flags.iter().rev() { // Swap back to big-endian + f.write(w)?; } + Ok(()) } macro_rules! impl_feature_len_prefixed_write { ($features: ident) => { impl Writeable for $features { fn write(&self, w: &mut W) -> Result<(), io::Error> { - (self.flags.len() as u16).write(w)?; - self.write_be(w) + let bytes = self.le_flags(); + (bytes.len() as u16).write(w)?; + write_be(w, bytes) } } impl Readable for $features { @@ -1006,221 +77,37 @@ impl_feature_tlv_write!(ChannelTypeFeatures); // Some features may appear both in a TLV record and as part of a TLV subtype sequence. The latter // requires a length but the former does not. -impl Writeable for WithoutLength<&Features> { - fn write(&self, w: &mut W) -> Result<(), io::Error> { - self.0.write_be(w) - } -} +macro_rules! impl_feature_write_without_length { + ($features: ident) => { + impl Writeable for WithoutLength<&$features> { + fn write(&self, w: &mut W) -> Result<(), io::Error> { + write_be(w, self.0.le_flags()) + } + } -impl Readable for WithoutLength> { - fn read(r: &mut R) -> Result { - let v = io_extras::read_to_end(r)?; - Ok(WithoutLength(Features::::from_be_bytes(v))) + impl Readable for WithoutLength<$features> { + fn read(r: &mut R) -> Result { + let v = io_extras::read_to_end(r)?; + Ok(WithoutLength($features::from_be_bytes(v))) + } + } } } -pub(crate) fn unset_features_mask_at_position(other: &Features, index: usize) -> u8 { - if index < other.flags.len() { - // Form a mask similar to !T::KNOWN_FEATURE_MASK only for `other` - !(other.flags[index] - | ((other.flags[index] >> 1) & ANY_REQUIRED_FEATURES_MASK) - | ((other.flags[index] << 1) & ANY_OPTIONAL_FEATURES_MASK)) - } else { - 0b11_11_11_11 - } -} +impl_feature_write_without_length!(Bolt12InvoiceFeatures); +impl_feature_write_without_length!(ChannelTypeFeatures); +impl_feature_write_without_length!(InvoiceRequestFeatures); +impl_feature_write_without_length!(OfferFeatures); #[cfg(test)] mod tests { - use super::{ChannelFeatures, ChannelTypeFeatures, InitFeatures, Bolt11InvoiceFeatures, NodeFeatures, OfferFeatures, sealed}; - use bech32::{Base32Len, FromBase32, ToBase32, u5}; + use super::*; use crate::util::ser::{Readable, WithoutLength, Writeable}; - #[test] - fn sanity_test_unknown_bits() { - let features = ChannelFeatures::empty(); - assert!(!features.requires_unknown_bits()); - assert!(!features.supports_unknown_bits()); - - let mut features = ChannelFeatures::empty(); - features.set_unknown_feature_required(); - assert!(features.requires_unknown_bits()); - assert!(features.supports_unknown_bits()); - assert_eq!(features.required_unknown_bits_from(&ChannelFeatures::empty()), vec![123456788]); - - let mut features = ChannelFeatures::empty(); - features.set_unknown_feature_optional(); - assert!(!features.requires_unknown_bits()); - assert!(features.supports_unknown_bits()); - assert_eq!(features.required_unknown_bits_from(&ChannelFeatures::empty()), vec![]); - - let mut features = ChannelFeatures::empty(); - features.set_unknown_feature_required(); - features.set_custom_bit(123456786).unwrap(); - assert!(features.requires_unknown_bits()); - assert!(features.supports_unknown_bits()); - assert_eq!(features.required_unknown_bits_from(&ChannelFeatures::empty()), vec![123456786, 123456788]); - - let mut limiter = ChannelFeatures::empty(); - limiter.set_unknown_feature_optional(); - assert_eq!(features.required_unknown_bits_from(&limiter), vec![123456786]); - } - - #[test] - fn requires_unknown_bits_from() { - let mut features1 = InitFeatures::empty(); - let mut features2 = InitFeatures::empty(); - assert!(!features1.requires_unknown_bits_from(&features2)); - assert!(!features2.requires_unknown_bits_from(&features1)); - - features1.set_data_loss_protect_required(); - assert!(features1.requires_unknown_bits_from(&features2)); - assert!(!features2.requires_unknown_bits_from(&features1)); - - features2.set_data_loss_protect_optional(); - assert!(!features1.requires_unknown_bits_from(&features2)); - assert!(!features2.requires_unknown_bits_from(&features1)); - - features2.set_gossip_queries_required(); - assert!(!features1.requires_unknown_bits_from(&features2)); - assert!(features2.requires_unknown_bits_from(&features1)); - - features1.set_gossip_queries_optional(); - assert!(!features1.requires_unknown_bits_from(&features2)); - assert!(!features2.requires_unknown_bits_from(&features1)); - - features1.set_variable_length_onion_required(); - assert!(features1.requires_unknown_bits_from(&features2)); - assert!(!features2.requires_unknown_bits_from(&features1)); - - features2.set_variable_length_onion_optional(); - assert!(!features1.requires_unknown_bits_from(&features2)); - assert!(!features2.requires_unknown_bits_from(&features1)); - - features1.set_basic_mpp_required(); - features2.set_wumbo_required(); - assert!(features1.requires_unknown_bits_from(&features2)); - assert!(features2.requires_unknown_bits_from(&features1)); - } - - #[test] - fn convert_to_context_with_relevant_flags() { - let mut init_features = InitFeatures::empty(); - // Set a bunch of features we use, plus initial_routing_sync_required (which shouldn't get - // converted as it's only relevant in an init context). - init_features.set_initial_routing_sync_required(); - init_features.set_data_loss_protect_required(); - init_features.set_variable_length_onion_required(); - init_features.set_static_remote_key_required(); - init_features.set_payment_secret_required(); - init_features.set_basic_mpp_optional(); - init_features.set_wumbo_optional(); - init_features.set_anchors_zero_fee_htlc_tx_optional(); - init_features.set_route_blinding_optional(); - init_features.set_shutdown_any_segwit_optional(); - init_features.set_onion_messages_optional(); - init_features.set_channel_type_optional(); - init_features.set_scid_privacy_optional(); - init_features.set_zero_conf_optional(); - - assert!(init_features.initial_routing_sync()); - assert!(!init_features.supports_upfront_shutdown_script()); - assert!(!init_features.supports_gossip_queries()); - - let node_features: NodeFeatures = init_features.to_context(); - { - // Check that the flags are as expected: - // - option_data_loss_protect (req) - // - var_onion_optin (req) | static_remote_key (req) | payment_secret(req) - // - basic_mpp | wumbo | option_anchors_zero_fee_htlc_tx - // - option_route_blinding | opt_shutdown_anysegwit - // - onion_messages - // - option_channel_type | option_scid_alias - // - option_zeroconf - assert_eq!(node_features.flags.len(), 7); - assert_eq!(node_features.flags[0], 0b00000001); - assert_eq!(node_features.flags[1], 0b01010001); - assert_eq!(node_features.flags[2], 0b10001010); - assert_eq!(node_features.flags[3], 0b00001010); - assert_eq!(node_features.flags[4], 0b10000000); - assert_eq!(node_features.flags[5], 0b10100000); - assert_eq!(node_features.flags[6], 0b00001000); - } - - // Check that cleared flags are kept blank when converting back: - // - initial_routing_sync was not applicable to NodeContext - // - upfront_shutdown_script was cleared before converting - // - gossip_queries was cleared before converting - let features: InitFeatures = node_features.to_context_internal(); - assert!(!features.initial_routing_sync()); - assert!(!features.supports_upfront_shutdown_script()); - assert!(!init_features.supports_gossip_queries()); - } - - #[test] - fn convert_to_context_with_unknown_flags() { - // Ensure the `from` context has fewer known feature bytes than the `to` context. - assert!(::KNOWN_FEATURE_MASK.len() < - ::KNOWN_FEATURE_MASK.len()); - let mut channel_features = ChannelFeatures::empty(); - channel_features.set_unknown_feature_optional(); - assert!(channel_features.supports_unknown_bits()); - let invoice_features: Bolt11InvoiceFeatures = channel_features.to_context_internal(); - assert!(!invoice_features.supports_unknown_bits()); - } - - #[test] - fn set_feature_bits() { - let mut features = Bolt11InvoiceFeatures::empty(); - features.set_basic_mpp_optional(); - features.set_payment_secret_required(); - assert!(features.supports_basic_mpp()); - assert!(!features.requires_basic_mpp()); - assert!(features.requires_payment_secret()); - assert!(features.supports_payment_secret()); - - // Set flags manually - let mut features = NodeFeatures::empty(); - assert!(features.set_optional_feature_bit(55).is_ok()); - assert!(features.supports_keysend()); - assert!(features.set_optional_feature_bit(255).is_ok()); - assert!(features.set_required_feature_bit(256).is_err()); - } - - #[test] - fn set_custom_bits() { - let mut features = Bolt11InvoiceFeatures::empty(); - features.set_variable_length_onion_optional(); - assert_eq!(features.flags[1], 0b00000010); - - assert!(features.set_optional_custom_bit(255).is_err()); - assert!(features.set_required_custom_bit(256).is_ok()); - assert!(features.set_required_custom_bit(258).is_ok()); - assert_eq!(features.flags[31], 0b00000000); - assert_eq!(features.flags[32], 0b00000101); - - let known_bit = ::EVEN_BIT; - let byte_offset = ::BYTE_OFFSET; - assert_eq!(byte_offset, 1); - assert_eq!(features.flags[byte_offset], 0b00000010); - assert!(features.set_required_custom_bit(known_bit).is_err()); - assert_eq!(features.flags[byte_offset], 0b00000010); - - let mut features = Bolt11InvoiceFeatures::empty(); - assert!(features.set_optional_custom_bit(256).is_ok()); - assert!(features.set_optional_custom_bit(259).is_ok()); - assert_eq!(features.flags[32], 0b00001010); - - let mut features = Bolt11InvoiceFeatures::empty(); - assert!(features.set_required_custom_bit(257).is_ok()); - assert!(features.set_required_custom_bit(258).is_ok()); - assert_eq!(features.flags[32], 0b00000101); - } - #[test] fn encodes_features_without_length() { let features = OfferFeatures::from_le_bytes(vec![1, 2, 3, 4, 5, 42, 100, 101]); - assert_eq!(features.flags.len(), 8); + assert_eq!(features.le_flags().len(), 8); let mut serialized_features = Vec::new(); WithoutLength(&features).write(&mut serialized_features).unwrap(); @@ -1230,69 +117,4 @@ mod tests { WithoutLength::::read(&mut &serialized_features[..]).unwrap().0; assert_eq!(features, deserialized_features); } - - #[test] - fn invoice_features_encoding() { - let features_as_u5s = vec![ - u5::try_from_u8(6).unwrap(), - u5::try_from_u8(10).unwrap(), - u5::try_from_u8(25).unwrap(), - u5::try_from_u8(1).unwrap(), - u5::try_from_u8(10).unwrap(), - u5::try_from_u8(0).unwrap(), - u5::try_from_u8(20).unwrap(), - u5::try_from_u8(2).unwrap(), - u5::try_from_u8(0).unwrap(), - u5::try_from_u8(6).unwrap(), - u5::try_from_u8(0).unwrap(), - u5::try_from_u8(16).unwrap(), - u5::try_from_u8(1).unwrap(), - ]; - let features = Bolt11InvoiceFeatures::from_le_bytes(vec![1, 2, 3, 4, 5, 42, 100, 101]); - - // Test length calculation. - assert_eq!(features.base32_len(), 13); - - // Test serialization. - let features_serialized = features.to_base32(); - assert_eq!(features_as_u5s, features_serialized); - - // Test deserialization. - let features_deserialized = Bolt11InvoiceFeatures::from_base32(&features_as_u5s).unwrap(); - assert_eq!(features, features_deserialized); - } - - #[test] - fn test_channel_type_mapping() { - // If we map an Bolt11InvoiceFeatures with StaticRemoteKey optional, it should map into a - // required-StaticRemoteKey ChannelTypeFeatures. - let mut init_features = InitFeatures::empty(); - init_features.set_static_remote_key_optional(); - let converted_features = ChannelTypeFeatures::from_init(&init_features); - assert_eq!(converted_features, ChannelTypeFeatures::only_static_remote_key()); - assert!(!converted_features.supports_any_optional_bits()); - assert!(converted_features.requires_static_remote_key()); - } - - #[test] - #[cfg(feature = "std")] - fn test_excess_zero_bytes_ignored() { - // Checks that `Hash` and `PartialEq` ignore excess zero bytes, which may appear due to - // feature conversion or because a peer serialized their feature poorly. - use std::collections::hash_map::DefaultHasher; - use std::hash::{Hash, Hasher}; - - let mut zerod_features = InitFeatures::empty(); - zerod_features.flags = vec![0]; - let empty_features = InitFeatures::empty(); - assert!(empty_features.flags.is_empty()); - - assert_eq!(zerod_features, empty_features); - - let mut zerod_hash = DefaultHasher::new(); - zerod_features.hash(&mut zerod_hash); - let mut empty_hash = DefaultHasher::new(); - empty_features.hash(&mut empty_hash); - assert_eq!(zerod_hash.finish(), empty_hash.finish()); - } } diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index 5cc1257c461..85f1ae0aa48 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -2320,11 +2320,26 @@ impl_writeable_msg!(ChannelReady, { (1, short_channel_id_alias, option), }); +pub(crate) fn write_features_up_to_13(w: &mut W, le_flags: &[u8]) -> Result<(), io::Error> { + let len = core::cmp::min(2, le_flags.len()); + (len as u16).write(w)?; + for i in (0..len).rev() { + if i == 0 { + le_flags[i].write(w)?; + } else { + // On byte 1, we want up-to-and-including-bit-13, 0-indexed, which is + // up-to-and-including-bit-5, 0-indexed, on this byte: + (le_flags[i] & 0b00_11_11_11).write(w)?; + } + } + Ok(()) +} + impl Writeable for Init { fn write(&self, w: &mut W) -> Result<(), io::Error> { // global_features gets the bottom 13 bits of our features, and local_features gets all of // our relevant feature bits. This keeps us compatible with old nodes. - self.features.write_up_to_13(w)?; + write_features_up_to_13(w, self.features.le_flags())?; self.features.write(w)?; encode_tlv_stream!(w, { (1, self.networks.as_ref().map(|n| WithoutLength(n)), option), From 3b3774ee693c417b5131f79b0c34cca798b4774a Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Fri, 9 Aug 2024 01:26:21 +0000 Subject: [PATCH 05/11] Move `UntrustedString` and `PrintableString` to `lightning-types` `lightning-invoice` currently has a dependency on the entire `lightning` crate just because it wants to use some of the useful types from it. This is obviously backwards and leads to some awkwardness like the BOLT 11 invoice signing API in the `lightning` crate taking a `[u5]` rather than a `Bolt11Invoice`. This takes one more step, moving the `UntrustedString` and `PrintableString` types to `lightning-types`. --- lightning-types/src/lib.rs | 1 + .../util => lightning-types/src}/string.rs | 20 +------------------ lightning/src/util/mod.rs | 5 ++++- lightning/src/util/ser.rs | 12 +++++++++++ 4 files changed, 18 insertions(+), 20 deletions(-) rename {lightning/src/util => lightning-types/src}/string.rs (77%) diff --git a/lightning-types/src/lib.rs b/lightning-types/src/lib.rs index bcfb2b62d43..1539401d383 100644 --- a/lightning-types/src/lib.rs +++ b/lightning-types/src/lib.rs @@ -26,3 +26,4 @@ extern crate core; pub mod features; pub mod payment; pub mod routing; +pub mod string; diff --git a/lightning/src/util/string.rs b/lightning-types/src/string.rs similarity index 77% rename from lightning/src/util/string.rs rename to lightning-types/src/string.rs index ab12486a0d8..ae5395a5289 100644 --- a/lightning/src/util/string.rs +++ b/lightning-types/src/string.rs @@ -9,31 +9,13 @@ //! Utilities for strings. +use alloc::string::String; use core::fmt; -use crate::io::{self, Read}; -use crate::ln::msgs; -use crate::util::ser::{Writeable, Writer, Readable}; - -#[allow(unused_imports)] -use crate::prelude::*; /// Struct to `Display` fields in a safe way using `PrintableString` #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Default)] pub struct UntrustedString(pub String); -impl Writeable for UntrustedString { - fn write(&self, w: &mut W) -> Result<(), io::Error> { - self.0.write(w) - } -} - -impl Readable for UntrustedString { - fn read(r: &mut R) -> Result { - let s: String = Readable::read(r)?; - Ok(Self(s)) - } -} - impl fmt::Display for UntrustedString { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { PrintableString(&self.0).fmt(f) diff --git a/lightning/src/util/mod.rs b/lightning/src/util/mod.rs index a81a36c5583..cfcea837971 100644 --- a/lightning/src/util/mod.rs +++ b/lightning/src/util/mod.rs @@ -21,7 +21,6 @@ pub mod message_signing; pub mod invoice; pub mod persist; pub mod scid_utils; -pub mod string; pub mod sweep; pub mod wakers; #[cfg(fuzzing)] @@ -54,3 +53,7 @@ pub mod test_utils; #[cfg(any(test, feature = "_test_utils"))] pub mod test_channel_signer; +pub mod string { + //! Utilities to wrap untrusted strings and handle them (more) safely + pub use lightning_types::string::{PrintableString, UntrustedString}; +} diff --git a/lightning/src/util/ser.rs b/lightning/src/util/ser.rs index 3d3d97252f2..29c52f6a08b 100644 --- a/lightning/src/util/ser.rs +++ b/lightning/src/util/ser.rs @@ -627,6 +627,18 @@ impl<'a> From<&'a String> for WithoutLength<&'a String> { fn from(s: &'a String) -> Self { Self(s) } } +impl Writeable for UntrustedString { + fn write(&self, w: &mut W) -> Result<(), io::Error> { + self.0.write(w) + } +} + +impl Readable for UntrustedString { + fn read(r: &mut R) -> Result { + let s: String = Readable::read(r)?; + Ok(Self(s)) + } +} impl Writeable for WithoutLength<&UntrustedString> { #[inline] From 1f01f2ef672ba6054b0cd0ba2606960cb550ab0c Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Fri, 9 Aug 2024 13:20:35 +0000 Subject: [PATCH 06/11] Use `check_added_monitors` test utility in invoice utils tests In a coming commit, the `lightning-invoice::utils` module will move to the `lightning` crate, causing its tests to be included in the global lockorder tests done in that crate. This should be fine, except that the `lightning-invoice::utils` module currently holds the `added_monitors` lock too long causing lockorder violations. Instead, this commit replaces the legacy monitors-added test with the `check_added_monitors` test utility. --- lightning-invoice/src/utils.rs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/lightning-invoice/src/utils.rs b/lightning-invoice/src/utils.rs index 669e6f50f1a..5b94c3adac3 100644 --- a/lightning-invoice/src/utils.rs +++ b/lightning-invoice/src/utils.rs @@ -905,20 +905,15 @@ mod test { nodes[0].node.send_payment(payment_hash, RecipientOnionFields::secret_only(*invoice.payment_secret()), PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap(); - let mut added_monitors = nodes[0].chain_monitor.added_monitors.lock().unwrap(); - assert_eq!(added_monitors.len(), 1); - added_monitors.clear(); + check_added_monitors(&nodes[0], 1); let mut events = nodes[0].node.get_and_clear_pending_msg_events(); assert_eq!(events.len(), 1); SendEvent::from_event(events.remove(0)) - }; nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]); nodes[1].node.handle_commitment_signed(&nodes[0].node.get_our_node_id(), &payment_event.commitment_msg); - let mut added_monitors = nodes[1].chain_monitor.added_monitors.lock().unwrap(); - assert_eq!(added_monitors.len(), 1); - added_monitors.clear(); + check_added_monitors(&nodes[1], 1); let events = nodes[1].node.get_and_clear_pending_msg_events(); assert_eq!(events.len(), 2); } @@ -1362,9 +1357,7 @@ mod test { nodes[0].node.send_payment(payment_hash, RecipientOnionFields::secret_only(*invoice.payment_secret()), PaymentId(payment_hash.0), params, Retry::Attempts(0)).unwrap(); - let mut added_monitors = nodes[0].chain_monitor.added_monitors.lock().unwrap(); - assert_eq!(added_monitors.len(), 1); - added_monitors.clear(); + check_added_monitors(&nodes[0], 1); let mut events = nodes[0].node.get_and_clear_pending_msg_events(); assert_eq!(events.len(), 1); From 1eba737f442fba62ce25a34b2a857d6b9df484fc Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Fri, 9 Aug 2024 01:13:25 +0000 Subject: [PATCH 07/11] Add a `lightning-types` dependency to `lightning-invoice` `lightning-invoice` currently has a dependency on the entire `lightning` crate just because it wants to use some of the useful types from it. This is obviously backwards and leads to some awkwardness like the BOLT 11 invoice signing API in the `lightning` crate taking a `[u5]` rather than a `Bolt11Invoice`. This takes tees us up for the final step, adding a `lightning-types` dependency to `lightning-invoice` and using it for imports rather than the `lightning` crate. --- lightning-invoice/Cargo.toml | 1 + lightning-invoice/src/de.rs | 10 ++++------ lightning-invoice/src/lib.rs | 21 ++++++++++----------- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/lightning-invoice/Cargo.toml b/lightning-invoice/Cargo.toml index 94b3071676a..0f5f464b83a 100644 --- a/lightning-invoice/Cargo.toml +++ b/lightning-invoice/Cargo.toml @@ -21,6 +21,7 @@ std = ["bitcoin/std", "lightning/std", "bech32/std"] [dependencies] bech32 = { version = "0.9.1", default-features = false } +lightning-types = { version = "0.1", path = "../lightning-types", default-features = false } lightning = { version = "0.0.123-beta", path = "../lightning", default-features = false } secp256k1 = { version = "0.28.0", default-features = false, features = ["recovery", "alloc"] } serde = { version = "1.0.118", optional = true } diff --git a/lightning-invoice/src/de.rs b/lightning-invoice/src/de.rs index bd9f4a5f6de..be1a21aa25f 100644 --- a/lightning-invoice/src/de.rs +++ b/lightning-invoice/src/de.rs @@ -14,9 +14,8 @@ use bitcoin::{PubkeyHash, ScriptHash, WitnessVersion}; use bitcoin::hashes::Hash; use bitcoin::hashes::sha256; use crate::prelude::*; -use lightning::ln::types::PaymentSecret; -use lightning::routing::gossip::RoutingFees; -use lightning::routing::router::{RouteHint, RouteHintHop}; +use lightning_types::payment::PaymentSecret; +use lightning_types::routing::{RoutingFees, RouteHint, RouteHintHop}; use secp256k1::ecdsa::{RecoveryId, RecoverableSignature}; use secp256k1::PublicKey; @@ -918,8 +917,7 @@ mod test { #[test] fn test_parse_route() { - use lightning::routing::gossip::RoutingFees; - use lightning::routing::router::{RouteHint, RouteHintHop}; + use lightning_types::routing::{RoutingFees, RouteHint, RouteHintHop}; use crate::PrivateRoute; use bech32::FromBase32; @@ -974,7 +972,7 @@ mod test { #[test] fn test_payment_secret_and_features_de_and_ser() { - use lightning::ln::features::Bolt11InvoiceFeatures; + use lightning_types::features::Bolt11InvoiceFeatures; use secp256k1::ecdsa::{RecoveryId, RecoverableSignature}; use crate::TaggedField::*; use crate::{SiPrefix, SignedRawBolt11Invoice, Bolt11InvoiceSignature, RawBolt11Invoice, RawHrp, RawDataPart, diff --git a/lightning-invoice/src/lib.rs b/lightning-invoice/src/lib.rs index 2df015d1a08..0784911fc61 100644 --- a/lightning-invoice/src/lib.rs +++ b/lightning-invoice/src/lib.rs @@ -30,6 +30,7 @@ pub mod utils; extern crate bech32; #[macro_use] extern crate lightning; +extern crate lightning_types; extern crate secp256k1; extern crate alloc; #[cfg(any(test, feature = "std"))] @@ -44,7 +45,7 @@ use bech32::u5; use bitcoin::{Address, Network, PubkeyHash, ScriptHash, WitnessProgram, WitnessVersion}; use bitcoin::address::Payload; use bitcoin::hashes::{Hash, sha256}; -use lightning::ln::features::Bolt11InvoiceFeatures; +use lightning_types::features::Bolt11InvoiceFeatures; use lightning::util::invoice::construct_invoice_preimage; use secp256k1::PublicKey; @@ -64,12 +65,10 @@ use core::str; use serde::{Deserialize, Deserializer,Serialize, Serializer, de::Error}; #[doc(no_inline)] -pub use lightning::ln::types::PaymentSecret; +pub use lightning_types::payment::PaymentSecret; #[doc(no_inline)] -pub use lightning::routing::router::{RouteHint, RouteHintHop}; -#[doc(no_inline)] -pub use lightning::routing::gossip::RoutingFees; -use lightning::util::string::UntrustedString; +pub use lightning_types::routing::{RoutingFees, RouteHint, RouteHintHop}; +use lightning_types::string::UntrustedString; mod de; mod ser; @@ -161,7 +160,7 @@ pub const DEFAULT_MIN_FINAL_CLTV_EXPIRY_DELTA: u64 = 18; /// use secp256k1::Secp256k1; /// use secp256k1::SecretKey; /// -/// use lightning::ln::types::PaymentSecret; +/// use lightning_types::payment::PaymentSecret; /// /// use lightning_invoice::{Currency, InvoiceBuilder}; /// @@ -1877,14 +1876,14 @@ mod test { #[test] fn test_check_feature_bits() { use crate::TaggedField::*; - use lightning::ln::features::Bolt11InvoiceFeatures; + use lightning_types::features::Bolt11InvoiceFeatures; use secp256k1::Secp256k1; use secp256k1::SecretKey; use crate::{Bolt11Invoice, RawBolt11Invoice, RawHrp, RawDataPart, Currency, Sha256, PositiveTimestamp, Bolt11SemanticError}; let private_key = SecretKey::from_slice(&[42; 32]).unwrap(); - let payment_secret = lightning::ln::types::PaymentSecret([21; 32]); + let payment_secret = lightning_types::payment::PaymentSecret([21; 32]); let invoice_template = RawBolt11Invoice { hrp: RawHrp { currency: Currency::Bitcoin, @@ -1998,7 +1997,7 @@ mod test { #[test] fn test_builder_fail() { use crate::*; - use lightning::routing::router::RouteHintHop; + use lightning_types::routing::RouteHintHop; use std::iter::FromIterator; use secp256k1::PublicKey; @@ -2052,7 +2051,7 @@ mod test { #[test] fn test_builder_ok() { use crate::*; - use lightning::routing::router::RouteHintHop; + use lightning_types::routing::RouteHintHop; use secp256k1::Secp256k1; use secp256k1::{SecretKey, PublicKey}; use std::time::Duration; From a741a57249086d83812237ae8dc68c5d2ef6b725 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Fri, 9 Aug 2024 01:29:48 +0000 Subject: [PATCH 08/11] Swap the dep order between `lightning` and `lightning-invoice` `lightning-invoice` previously had a dependency on the entire `lightning` crate just because it wants to use some of the useful types from it. This is obviously backwards and leads to some awkwardness like the BOLT 11 invoice signing API in the `lightning` crate taking a `[u5]` rather than a `Bolt11Invoice`. Here we finally rectify this issue, swapping the dependency order and making `lightning` depend on `lightning-invoice` rather than the other way around. This moves various utilities which were in `lightning-invoice` but relied on `lightning` payment types to make payments to where they belong (the `lightning` crate), but doesn't bother with integrating them well in their new home. --- lightning-invoice/Cargo.toml | 6 +- lightning-invoice/src/lib.rs | 41 +++-- lightning-invoice/tests/ser_de.rs | 1 - lightning/Cargo.toml | 5 +- .../src/ln/bolt11_payment.rs | 32 ++-- .../src/ln/invoice_utils.rs | 155 +++++++++--------- lightning/src/ln/mod.rs | 5 + 7 files changed, 127 insertions(+), 118 deletions(-) rename lightning-invoice/src/payment.rs => lightning/src/ln/bolt11_payment.rs (88%) rename lightning-invoice/src/utils.rs => lightning/src/ln/invoice_utils.rs (94%) diff --git a/lightning-invoice/Cargo.toml b/lightning-invoice/Cargo.toml index 0f5f464b83a..f73321296d1 100644 --- a/lightning-invoice/Cargo.toml +++ b/lightning-invoice/Cargo.toml @@ -16,19 +16,17 @@ rustdoc-args = ["--cfg", "docsrs"] [features] default = ["std"] -no-std = ["lightning/no-std"] -std = ["bitcoin/std", "lightning/std", "bech32/std"] +no-std = ["bitcoin/no-std"] +std = ["bitcoin/std", "bech32/std"] [dependencies] bech32 = { version = "0.9.1", default-features = false } lightning-types = { version = "0.1", path = "../lightning-types", default-features = false } -lightning = { version = "0.0.123-beta", path = "../lightning", default-features = false } secp256k1 = { version = "0.28.0", default-features = false, features = ["recovery", "alloc"] } serde = { version = "1.0.118", optional = true } bitcoin = { version = "0.31.2", default-features = false } [dev-dependencies] -lightning = { version = "0.0.123-beta", path = "../lightning", default-features = false, features = ["_test_utils"] } serde_json = { version = "1"} hashbrown = { version = "0.13", default-features = false } diff --git a/lightning-invoice/src/lib.rs b/lightning-invoice/src/lib.rs index 0784911fc61..56ca21a9b45 100644 --- a/lightning-invoice/src/lib.rs +++ b/lightning-invoice/src/lib.rs @@ -25,11 +25,7 @@ #[cfg(not(any(feature = "std", feature = "no-std")))] compile_error!("at least one of the `std` or `no-std` features must be enabled"); -pub mod payment; -pub mod utils; - extern crate bech32; -#[macro_use] extern crate lightning; extern crate lightning_types; extern crate secp256k1; extern crate alloc; @@ -41,12 +37,11 @@ extern crate serde; #[cfg(feature = "std")] use std::time::SystemTime; -use bech32::u5; +use bech32::{FromBase32, u5}; use bitcoin::{Address, Network, PubkeyHash, ScriptHash, WitnessProgram, WitnessVersion}; use bitcoin::address::Payload; use bitcoin::hashes::{Hash, sha256}; use lightning_types::features::Bolt11InvoiceFeatures; -use lightning::util::invoice::construct_invoice_preimage; use secp256k1::PublicKey; use secp256k1::{Message, Secp256k1}; @@ -138,11 +133,9 @@ 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_DELTA`]. +/// Note that this is *not* the same value as rust-lightning's minimum CLTV expiry. /// /// [BOLT 11]: https://github.com/lightning/bolts/blob/master/11-payment-encoding.md -/// [`MIN_FINAL_CLTV_EXPIRY_DELTA`]: lightning::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA pub const DEFAULT_MIN_FINAL_CLTV_EXPIRY_DELTA: u64 = 18; /// Builder for [`Bolt11Invoice`]s. It's the most convenient and advised way to use this library. It @@ -150,7 +143,6 @@ pub const DEFAULT_MIN_FINAL_CLTV_EXPIRY_DELTA: u64 = 18; /// /// ``` /// extern crate secp256k1; -/// extern crate lightning; /// extern crate lightning_invoice; /// extern crate bitcoin; /// @@ -969,7 +961,23 @@ macro_rules! find_all_extract { impl RawBolt11Invoice { /// Hash the HRP as bytes and signatureless data part. fn hash_from_parts(hrp_bytes: &[u8], data_without_signature: &[u5]) -> [u8; 32] { - let preimage = construct_invoice_preimage(hrp_bytes, data_without_signature); + let mut preimage = Vec::::from(hrp_bytes); + + let mut data_part = Vec::from(data_without_signature); + let overhang = (data_part.len() * 5) % 8; + if overhang > 0 { + // add padding if data does not end at a byte boundary + data_part.push(u5::try_from_u8(0).unwrap()); + + // if overhang is in (1..3) we need to add u5(0) padding two times + if overhang < 3 { + data_part.push(u5::try_from_u8(0).unwrap()); + } + } + + preimage.extend_from_slice(&Vec::::from_base32(&data_part) + .expect("No padding error may occur due to appended zero above.")); + let mut hash: [u8; 32] = Default::default(); hash.copy_from_slice(&sha256::Hash::hash(&preimage)[..]); hash @@ -1635,15 +1643,12 @@ pub enum CreationError { /// The supplied millisatoshi amount was greater than the total bitcoin supply. InvalidAmount, - /// Route hints were required for this invoice and were missing. Applies to - /// [phantom invoices]. - /// - /// [phantom invoices]: crate::utils::create_phantom_invoice + // TODO: These two errors are really errors with things in the `lightning` crate and thus + // shouldn't live here. + /// Route hints were required for this invoice and were missing. MissingRouteHints, - /// The provided `min_final_cltv_expiry_delta` was less than [`MIN_FINAL_CLTV_EXPIRY_DELTA`]. - /// - /// [`MIN_FINAL_CLTV_EXPIRY_DELTA`]: lightning::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA + /// The provided `min_final_cltv_expiry_delta` was less than rust-lightning's minimum. MinFinalCltvExpiryDeltaTooShort, } diff --git a/lightning-invoice/tests/ser_de.rs b/lightning-invoice/tests/ser_de.rs index d0058c9a1e3..d9d6ad4c195 100644 --- a/lightning-invoice/tests/ser_de.rs +++ b/lightning-invoice/tests/ser_de.rs @@ -1,5 +1,4 @@ extern crate bech32; -extern crate lightning; extern crate lightning_invoice; extern crate secp256k1; diff --git a/lightning/Cargo.toml b/lightning/Cargo.toml index b55b675b065..c6d82113053 100644 --- a/lightning/Cargo.toml +++ b/lightning/Cargo.toml @@ -31,8 +31,8 @@ unsafe_revoked_tx_signing = [] # Override signing to not include randomness when generating signatures for test vectors. _test_vectors = [] -no-std = ["hashbrown", "possiblyrandom", "bitcoin/no-std", "core2/alloc", "libm"] -std = ["bitcoin/std", "bech32/std"] +no-std = ["hashbrown", "possiblyrandom", "bitcoin/no-std", "lightning-invoice/no-std", "core2/alloc", "libm"] +std = ["bitcoin/std", "bech32/std", "lightning-invoice/std"] # Generates low-r bitcoin signatures, which saves 1 byte in 50% of the cases grind_signatures = [] @@ -41,6 +41,7 @@ default = ["std", "grind_signatures"] [dependencies] lightning-types = { version = "0.1", path = "../lightning-types", default-features = false } +lightning-invoice = { version = "0.31.0-beta", path = "../lightning-invoice", default-features = false } bech32 = { version = "0.9.1", default-features = false } bitcoin = { version = "0.31.2", default-features = false, features = ["secp-recovery"] } diff --git a/lightning-invoice/src/payment.rs b/lightning/src/ln/bolt11_payment.rs similarity index 88% rename from lightning-invoice/src/payment.rs rename to lightning/src/ln/bolt11_payment.rs index a8ffce7bbd9..5c5e3711abd 100644 --- a/lightning-invoice/src/payment.rs +++ b/lightning/src/ln/bolt11_payment.rs @@ -9,12 +9,12 @@ //! Convenient utilities for paying Lightning invoices. -use crate::Bolt11Invoice; use bitcoin::hashes::Hash; +use lightning_invoice::Bolt11Invoice; -use lightning::ln::types::PaymentHash; -use lightning::ln::channelmanager::RecipientOnionFields; -use lightning::routing::router::{PaymentParameters, RouteParameters}; +use crate::ln::channelmanager::RecipientOnionFields; +use crate::ln::types::PaymentHash; +use crate::routing::router::{PaymentParameters, RouteParameters}; /// Builds the necessary parameters to pay or pre-flight probe the given zero-amount /// [`Bolt11Invoice`] using [`ChannelManager::send_payment`] or @@ -26,8 +26,8 @@ use lightning::routing::router::{PaymentParameters, RouteParameters}; /// Will always succeed unless the invoice has an amount specified, in which case /// [`payment_parameters_from_invoice`] should be used. /// -/// [`ChannelManager::send_payment`]: lightning::ln::channelmanager::ChannelManager::send_payment -/// [`ChannelManager::send_preflight_probes`]: lightning::ln::channelmanager::ChannelManager::send_preflight_probes +/// [`ChannelManager::send_payment`]: crate::ln::channelmanager::ChannelManager::send_payment +/// [`ChannelManager::send_preflight_probes`]: crate::ln::channelmanager::ChannelManager::send_preflight_probes pub fn payment_parameters_from_zero_amount_invoice(invoice: &Bolt11Invoice, amount_msat: u64) -> Result<(PaymentHash, RecipientOnionFields, RouteParameters), ()> { if invoice.amount_milli_satoshis().is_some() { @@ -46,8 +46,8 @@ pub fn payment_parameters_from_zero_amount_invoice(invoice: &Bolt11Invoice, amou /// Will always succeed unless the invoice has no amount specified, in which case /// [`payment_parameters_from_zero_amount_invoice`] should be used. /// -/// [`ChannelManager::send_payment`]: lightning::ln::channelmanager::ChannelManager::send_payment -/// [`ChannelManager::send_preflight_probes`]: lightning::ln::channelmanager::ChannelManager::send_preflight_probes +/// [`ChannelManager::send_payment`]: crate::ln::channelmanager::ChannelManager::send_payment +/// [`ChannelManager::send_preflight_probes`]: crate::ln::channelmanager::ChannelManager::send_preflight_probes pub fn payment_parameters_from_invoice(invoice: &Bolt11Invoice) -> Result<(PaymentHash, RecipientOnionFields, RouteParameters), ()> { if let Some(amount_msat) = invoice.amount_milli_satoshis() { @@ -83,11 +83,11 @@ fn params_from_invoice(invoice: &Bolt11Invoice, amount_msat: u64) #[cfg(test)] mod tests { use super::*; - use crate::{InvoiceBuilder, Currency}; + use lightning_invoice::{InvoiceBuilder, Currency}; use bitcoin::hashes::sha256::Hash as Sha256; - use lightning::ln::types::PaymentSecret; - use lightning::routing::router::Payee; - use secp256k1::{SecretKey, PublicKey, Secp256k1}; + use crate::ln::types::PaymentSecret; + use crate::routing::router::Payee; + use bitcoin::secp256k1::{SecretKey, PublicKey, Secp256k1}; use core::time::Duration; #[cfg(feature = "std")] use std::time::SystemTime; @@ -169,10 +169,10 @@ mod tests { #[test] #[cfg(feature = "std")] fn payment_metadata_end_to_end() { - use lightning::events::Event; - use lightning::ln::channelmanager::{Retry, PaymentId}; - use lightning::ln::msgs::ChannelMessageHandler; - use lightning::ln::functional_test_utils::*; + use crate::events::Event; + use crate::ln::channelmanager::{Retry, PaymentId}; + use crate::ln::msgs::ChannelMessageHandler; + use crate::ln::functional_test_utils::*; // Test that a payment metadata read from an invoice passed to `pay_invoice` makes it all // the way out through the `PaymentClaimable` event. let chanmon_cfgs = create_chanmon_cfgs(2); diff --git a/lightning-invoice/src/utils.rs b/lightning/src/ln/invoice_utils.rs similarity index 94% rename from lightning-invoice/src/utils.rs rename to lightning/src/ln/invoice_utils.rs index 5b94c3adac3..5e4ba508f75 100644 --- a/lightning-invoice/src/utils.rs +++ b/lightning/src/ln/invoice_utils.rs @@ -1,22 +1,24 @@ //! Convenient utilities to create an invoice. -use crate::{Bolt11Invoice, CreationError, Currency, InvoiceBuilder, SignOrCreationError}; +use lightning_invoice::{Bolt11Invoice, CreationError, Currency, InvoiceBuilder, SignOrCreationError}; +use lightning_invoice::{Description, Bolt11InvoiceDescription, Sha256}; + +use crate::prelude::*; -use crate::{prelude::*, Description, Bolt11InvoiceDescription, Sha256}; use bech32::ToBase32; use bitcoin::hashes::Hash; -use lightning::chain; -use lightning::chain::chaininterface::{BroadcasterInterface, FeeEstimator}; -use lightning::sign::{Recipient, NodeSigner, SignerProvider, EntropySource}; -use lightning::ln::types::{PaymentHash, PaymentSecret}; -use lightning::ln::channel_state::ChannelDetails; -use lightning::ln::channelmanager::{ChannelManager, MIN_FINAL_CLTV_EXPIRY_DELTA}; -use lightning::ln::channelmanager::{PhantomRouteHints, MIN_CLTV_EXPIRY_DELTA}; -use lightning::ln::inbound_payment::{create, create_from_hash, ExpandedKey}; -use lightning::routing::gossip::RoutingFees; -use lightning::routing::router::{RouteHint, RouteHintHop, Router}; -use lightning::util::logger::{Logger, Record}; -use secp256k1::PublicKey; +use crate::chain; +use crate::chain::chaininterface::{BroadcasterInterface, FeeEstimator}; +use crate::sign::{Recipient, NodeSigner, SignerProvider, EntropySource}; +use crate::ln::types::{PaymentHash, PaymentSecret}; +use crate::ln::channel_state::ChannelDetails; +use crate::ln::channelmanager::{ChannelManager, MIN_FINAL_CLTV_EXPIRY_DELTA}; +use crate::ln::channelmanager::{PhantomRouteHints, MIN_CLTV_EXPIRY_DELTA}; +use crate::ln::inbound_payment::{create, create_from_hash, ExpandedKey}; +use crate::routing::gossip::RoutingFees; +use crate::routing::router::{RouteHint, RouteHintHop, Router}; +use crate::util::logger::{Logger, Record}; +use bitcoin::secp256k1::PublicKey; use alloc::collections::{btree_map, BTreeMap}; use core::ops::Deref; use core::time::Duration; @@ -54,12 +56,12 @@ use core::iter::Iterator; /// invoices in its `sign_invoice` implementation ([`PhantomKeysManager`] satisfies this /// requirement). /// -/// [`PhantomKeysManager`]: lightning::sign::PhantomKeysManager -/// [`ChannelManager::get_phantom_route_hints`]: lightning::ln::channelmanager::ChannelManager::get_phantom_route_hints -/// [`ChannelManager::create_inbound_payment`]: lightning::ln::channelmanager::ChannelManager::create_inbound_payment -/// [`ChannelManager::create_inbound_payment_for_hash`]: lightning::ln::channelmanager::ChannelManager::create_inbound_payment_for_hash -/// [`PhantomRouteHints::channels`]: lightning::ln::channelmanager::PhantomRouteHints::channels -/// [`MIN_FINAL_CLTV_EXPIRY_DETLA`]: lightning::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA +/// [`PhantomKeysManager`]: crate::sign::PhantomKeysManager +/// [`ChannelManager::get_phantom_route_hints`]: crate::ln::channelmanager::ChannelManager::get_phantom_route_hints +/// [`ChannelManager::create_inbound_payment`]: crate::ln::channelmanager::ChannelManager::create_inbound_payment +/// [`ChannelManager::create_inbound_payment_for_hash`]: crate::ln::channelmanager::ChannelManager::create_inbound_payment_for_hash +/// [`PhantomRouteHints::channels`]: crate::ln::channelmanager::PhantomRouteHints::channels +/// [`MIN_FINAL_CLTV_EXPIRY_DETLA`]: crate::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA /// /// This can be used in a `no_std` environment, where [`std::time::SystemTime`] is not /// available and the current time is supplied by the caller. @@ -111,11 +113,11 @@ where /// invoices in its `sign_invoice` implementation ([`PhantomKeysManager`] satisfies this /// requirement). /// -/// [`PhantomKeysManager`]: lightning::sign::PhantomKeysManager -/// [`ChannelManager::get_phantom_route_hints`]: lightning::ln::channelmanager::ChannelManager::get_phantom_route_hints -/// [`ChannelManager::create_inbound_payment`]: lightning::ln::channelmanager::ChannelManager::create_inbound_payment -/// [`ChannelManager::create_inbound_payment_for_hash`]: lightning::ln::channelmanager::ChannelManager::create_inbound_payment_for_hash -/// [`PhantomRouteHints::channels`]: lightning::ln::channelmanager::PhantomRouteHints::channels +/// [`PhantomKeysManager`]: crate::sign::PhantomKeysManager +/// [`ChannelManager::get_phantom_route_hints`]: crate::ln::channelmanager::ChannelManager::get_phantom_route_hints +/// [`ChannelManager::create_inbound_payment`]: crate::ln::channelmanager::ChannelManager::create_inbound_payment +/// [`ChannelManager::create_inbound_payment_for_hash`]: crate::ln::channelmanager::ChannelManager::create_inbound_payment_for_hash +/// [`PhantomRouteHints::channels`]: crate::ln::channelmanager::PhantomRouteHints::channels /// /// This can be used in a `no_std` environment, where [`std::time::SystemTime`] is not /// available and the current time is supplied by the caller. @@ -161,7 +163,7 @@ where let invoice = match description { Bolt11InvoiceDescription::Direct(description) => { - InvoiceBuilder::new(network).description(description.0.0.clone()) + InvoiceBuilder::new(network).description(description.as_inner().0.clone()) } Bolt11InvoiceDescription::Hash(hash) => InvoiceBuilder::new(network).description_hash(hash.0), }; @@ -234,7 +236,7 @@ where /// * Select up to three channels per node. /// * Select one hint from each node, up to three hints or until we run out of hints. /// -/// [`PhantomKeysManager`]: lightning::sign::PhantomKeysManager +/// [`PhantomKeysManager`]: crate::sign::PhantomKeysManager fn select_phantom_hints(amt_msat: Option, phantom_route_hints: Vec, logger: L) -> impl Iterator where @@ -331,7 +333,7 @@ fn rotate_through_iterators>(mut vecs: Vec) -> impl /// Note that LDK will add a buffer of 3 blocks to the delta to allow for up to a few new block /// confirmations during routing. /// -/// [`MIN_FINAL_CLTV_EXPIRY_DETLA`]: lightning::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA +/// [`MIN_FINAL_CLTV_EXPIRY_DETLA`]: crate::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA pub fn create_invoice_from_channelmanager( channelmanager: &ChannelManager, node_signer: NS, logger: L, network: Currency, amt_msat: Option, description: String, invoice_expiry_delta_secs: u32, @@ -372,7 +374,7 @@ where /// Note that LDK will add a buffer of 3 blocks to the delta to allow for up to a few new block /// confirmations during routing. /// -/// [`MIN_FINAL_CLTV_EXPIRY_DETLA`]: lightning::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA +/// [`MIN_FINAL_CLTV_EXPIRY_DETLA`]: crate::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA pub fn create_invoice_from_channelmanager_with_description_hash( channelmanager: &ChannelManager, node_signer: NS, logger: L, network: Currency, amt_msat: Option, description_hash: Sha256, @@ -541,7 +543,7 @@ fn _create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_has let invoice = match description { Bolt11InvoiceDescription::Direct(description) => { - InvoiceBuilder::new(network).description(description.0.0.clone()) + InvoiceBuilder::new(network).description(description.as_inner().0.clone()) } Bolt11InvoiceDescription::Hash(hash) => InvoiceBuilder::new(network).description_hash(hash.0), }; @@ -819,50 +821,49 @@ impl<'a, 'b, L: Deref> WithChannelDetails<'a, 'b, L> where L::Target: Logger { #[cfg(test)] mod test { + use super::*; use core::time::Duration; - use crate::{Currency, Description, Bolt11InvoiceDescription, SignOrCreationError, CreationError}; + use lightning_invoice::{Currency, Description, Bolt11InvoiceDescription, SignOrCreationError, CreationError}; use bitcoin::hashes::{Hash, sha256}; use bitcoin::hashes::sha256::Hash as Sha256; - use lightning::sign::PhantomKeysManager; - use lightning::events::{MessageSendEvent, MessageSendEventsProvider}; - use lightning::ln::types::PaymentHash; + use crate::sign::PhantomKeysManager; + use crate::events::{MessageSendEvent, MessageSendEventsProvider}; + use crate::ln::types::PaymentHash; #[cfg(feature = "std")] - use lightning::ln::types::PaymentPreimage; - use lightning::ln::channelmanager::{PhantomRouteHints, MIN_FINAL_CLTV_EXPIRY_DELTA, PaymentId, RecipientOnionFields, Retry}; - use lightning::ln::functional_test_utils::*; - use lightning::ln::msgs::ChannelMessageHandler; - use lightning::routing::router::{PaymentParameters, RouteParameters}; - use lightning::util::test_utils; - use lightning::util::config::UserConfig; - use crate::utils::{create_invoice_from_channelmanager_and_duration_since_epoch, rotate_through_iterators}; + use crate::ln::types::PaymentPreimage; + use crate::ln::channelmanager::{PhantomRouteHints, MIN_FINAL_CLTV_EXPIRY_DELTA, PaymentId, RecipientOnionFields, Retry}; + use crate::ln::functional_test_utils::*; + use crate::ln::msgs::ChannelMessageHandler; + use crate::routing::router::{PaymentParameters, RouteParameters}; + use crate::util::test_utils; + use crate::util::config::UserConfig; use std::collections::HashSet; - use lightning::util::string::UntrustedString; #[test] fn test_prefer_current_channel() { // No minimum, prefer larger candidate channel. - assert_eq!(crate::utils::prefer_current_channel(None, 100, 200), false); + assert_eq!(prefer_current_channel(None, 100, 200), false); // No minimum, prefer larger current channel. - assert_eq!(crate::utils::prefer_current_channel(None, 200, 100), true); + assert_eq!(prefer_current_channel(None, 200, 100), true); // Minimum set, prefer current channel over minimum + buffer. - assert_eq!(crate::utils::prefer_current_channel(Some(100), 115, 100), true); + assert_eq!(prefer_current_channel(Some(100), 115, 100), true); // Minimum set, prefer candidate channel over minimum + buffer. - assert_eq!(crate::utils::prefer_current_channel(Some(100), 105, 125), false); + assert_eq!(prefer_current_channel(Some(100), 105, 125), false); // Minimum set, both channels sufficient, prefer smaller current channel. - assert_eq!(crate::utils::prefer_current_channel(Some(100), 115, 125), true); + assert_eq!(prefer_current_channel(Some(100), 115, 125), true); // Minimum set, both channels sufficient, prefer smaller candidate channel. - assert_eq!(crate::utils::prefer_current_channel(Some(100), 200, 160), false); + assert_eq!(prefer_current_channel(Some(100), 200, 160), false); // Minimum set, neither sufficient, prefer larger current channel. - assert_eq!(crate::utils::prefer_current_channel(Some(200), 100, 50), true); + assert_eq!(prefer_current_channel(Some(200), 100, 50), true); // Minimum set, neither sufficient, prefer larger candidate channel. - assert_eq!(crate::utils::prefer_current_channel(Some(200), 100, 150), false); + assert_eq!(prefer_current_channel(Some(200), 100, 150), false); } @@ -878,10 +879,10 @@ mod test { nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Currency::BitcoinTestnet, Some(10_000), "test".to_string(), Duration::from_secs(1234567), non_default_invoice_expiry_secs, None).unwrap(); - assert_eq!(invoice.amount_pico_btc(), Some(100_000)); + assert_eq!(invoice.amount_milli_satoshis(), Some(10_000)); // If no `min_final_cltv_expiry_delta` is specified, then it should be `MIN_FINAL_CLTV_EXPIRY_DELTA`. assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64); - assert_eq!(invoice.description(), Bolt11InvoiceDescription::Direct(&Description(UntrustedString("test".to_string())))); + assert_eq!(invoice.description(), Bolt11InvoiceDescription::Direct(&Description::new("test".to_string()).unwrap())); assert_eq!(invoice.expiry_time(), Duration::from_secs(non_default_invoice_expiry_secs.into())); // Invoice SCIDs should always use inbound SCID aliases over the real channel ID, if one is @@ -925,7 +926,7 @@ mod test { let nodes = create_network(2, &node_cfgs, &node_chanmgrs); let custom_min_final_cltv_expiry_delta = Some(50); - let invoice = crate::utils::create_invoice_from_channelmanager_and_duration_since_epoch( + let invoice = create_invoice_from_channelmanager_and_duration_since_epoch( nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Currency::BitcoinTestnet, Some(10_000), "".into(), Duration::from_secs(1234567), 3600, if with_custom_delta { custom_min_final_cltv_expiry_delta } else { None }, @@ -948,7 +949,7 @@ mod test { let nodes = create_network(2, &node_cfgs, &node_chanmgrs); let custom_min_final_cltv_expiry_delta = Some(21); - let invoice = crate::utils::create_invoice_from_channelmanager_and_duration_since_epoch( + let invoice = create_invoice_from_channelmanager_and_duration_since_epoch( nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Currency::BitcoinTestnet, Some(10_000), "".into(), Duration::from_secs(1234567), 3600, custom_min_final_cltv_expiry_delta, @@ -962,14 +963,14 @@ mod test { let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); let nodes = create_network(2, &node_cfgs, &node_chanmgrs); - let description_hash = crate::Sha256(Hash::hash("Testing description_hash".as_bytes())); - let invoice = crate::utils::create_invoice_from_channelmanager_with_description_hash_and_duration_since_epoch( + let description_hash = Sha256(Hash::hash("Testing description_hash".as_bytes())); + let invoice = create_invoice_from_channelmanager_with_description_hash_and_duration_since_epoch( nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Currency::BitcoinTestnet, Some(10_000), description_hash, Duration::from_secs(1234567), 3600, None, ).unwrap(); - assert_eq!(invoice.amount_pico_btc(), Some(100_000)); + assert_eq!(invoice.amount_milli_satoshis(), Some(10_000)); assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64); - assert_eq!(invoice.description(), Bolt11InvoiceDescription::Hash(&crate::Sha256(Sha256::hash("Testing description_hash".as_bytes())))); + assert_eq!(invoice.description(), Bolt11InvoiceDescription::Hash(&Sha256(Sha256::hash("Testing description_hash".as_bytes())))); } #[test] @@ -979,14 +980,14 @@ mod test { let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); let nodes = create_network(2, &node_cfgs, &node_chanmgrs); let payment_hash = PaymentHash([0; 32]); - let invoice = crate::utils::create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_hash( + let invoice = create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_hash( nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Currency::BitcoinTestnet, Some(10_000), "test".to_string(), Duration::from_secs(1234567), 3600, payment_hash, None, ).unwrap(); - assert_eq!(invoice.amount_pico_btc(), Some(100_000)); + assert_eq!(invoice.amount_milli_satoshis(), Some(10_000)); assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64); - assert_eq!(invoice.description(), Bolt11InvoiceDescription::Direct(&Description(UntrustedString("test".to_string())))); + assert_eq!(invoice.description(), Bolt11InvoiceDescription::Direct(&Description::new("test".to_string()).unwrap())); assert_eq!(invoice.payment_hash(), &sha256::Hash::from_slice(&payment_hash.0[..]).unwrap()); } @@ -1278,7 +1279,7 @@ mod test { let hints = invoice.private_routes(); for hint in hints { - let hint_short_chan_id = (hint.0).0[0].short_channel_id; + let hint_short_chan_id = hint.0[0].short_channel_id; assert!(chan_ids_to_match.remove(&hint_short_chan_id)); } assert!(chan_ids_to_match.is_empty(), "Unmatched short channel ids: {:?}", chan_ids_to_match); @@ -1293,7 +1294,7 @@ mod test { #[cfg(feature = "std")] fn do_test_multi_node_receive(user_generated_pmt_hash: bool) { - use lightning::events::{Event, EventsProvider}; + use crate::events::{Event, EventsProvider}; use core::cell::RefCell; let mut chanmon_cfgs = create_chanmon_cfgs(3); @@ -1328,7 +1329,7 @@ mod test { let non_default_invoice_expiry_secs = 4200; let invoice = - crate::utils::create_phantom_invoice::<&test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestLogger>( + create_phantom_invoice::<&test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestLogger>( Some(payment_amt), payment_hash, "test".to_string(), non_default_invoice_expiry_secs, route_hints, nodes[1].keys_manager, nodes[1].keys_manager, nodes[1].logger, Currency::BitcoinTestnet, None, Duration::from_secs(genesis_timestamp) @@ -1341,7 +1342,7 @@ mod test { }; assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64); - assert_eq!(invoice.description(), Bolt11InvoiceDescription::Direct(&Description(UntrustedString("test".to_string())))); + assert_eq!(invoice.description(), Bolt11InvoiceDescription::Direct(&Description::new("test".to_string()).unwrap())); assert_eq!(invoice.route_hints().len(), 2); assert_eq!(invoice.expiry_time(), Duration::from_secs(non_default_invoice_expiry_secs.into())); assert!(!invoice.features().unwrap().supports_basic_mpp()); @@ -1421,7 +1422,7 @@ mod test { nodes[2].node.get_phantom_route_hints(), ]; - let invoice = crate::utils::create_phantom_invoice::<&test_utils::TestKeysInterface, + let invoice = create_phantom_invoice::<&test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestLogger>(Some(payment_amt), Some(payment_hash), "test".to_string(), 3600, route_hints, nodes[1].keys_manager, nodes[1].keys_manager, nodes[1].logger, Currency::BitcoinTestnet, None, Duration::from_secs(1234567)).unwrap(); @@ -1437,7 +1438,7 @@ mod test { #[test] #[cfg(feature = "std")] - fn create_phantom_invoice_with_description_hash() { + fn test_create_phantom_invoice_with_description_hash() { let chanmon_cfgs = create_chanmon_cfgs(3); let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]); @@ -1449,9 +1450,9 @@ mod test { nodes[2].node.get_phantom_route_hints(), ]; - let description_hash = crate::Sha256(Hash::hash("Description hash phantom invoice".as_bytes())); + let description_hash = Sha256(Hash::hash("Description hash phantom invoice".as_bytes())); let non_default_invoice_expiry_secs = 4200; - let invoice = crate::utils::create_phantom_invoice_with_description_hash::< + let invoice = create_phantom_invoice_with_description_hash::< &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestLogger, >( Some(payment_amt), None, non_default_invoice_expiry_secs, description_hash, @@ -1459,10 +1460,10 @@ mod test { Currency::BitcoinTestnet, None, Duration::from_secs(1234567), ) .unwrap(); - assert_eq!(invoice.amount_pico_btc(), Some(200_000)); + assert_eq!(invoice.amount_milli_satoshis(), Some(20_000)); assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64); assert_eq!(invoice.expiry_time(), Duration::from_secs(non_default_invoice_expiry_secs.into())); - assert_eq!(invoice.description(), Bolt11InvoiceDescription::Hash(&crate::Sha256(Sha256::hash("Description hash phantom invoice".as_bytes())))); + assert_eq!(invoice.description(), Bolt11InvoiceDescription::Hash(&Sha256(Sha256::hash("Description hash phantom invoice".as_bytes())))); } #[test] @@ -1483,11 +1484,11 @@ mod test { let non_default_invoice_expiry_secs = 4200; let min_final_cltv_expiry_delta = Some(100); let duration_since_epoch = Duration::from_secs(1234567); - let invoice = crate::utils::create_phantom_invoice::<&test_utils::TestKeysInterface, + let invoice = create_phantom_invoice::<&test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestLogger>(Some(payment_amt), payment_hash, "".to_string(), non_default_invoice_expiry_secs, route_hints, nodes[1].keys_manager, nodes[1].keys_manager, nodes[1].logger, Currency::BitcoinTestnet, min_final_cltv_expiry_delta, duration_since_epoch).unwrap(); - assert_eq!(invoice.amount_pico_btc(), Some(200_000)); + assert_eq!(invoice.amount_milli_satoshis(), Some(20_000)); assert_eq!(invoice.min_final_cltv_expiry_delta(), (min_final_cltv_expiry_delta.unwrap() + 3) as u64); assert_eq!(invoice.expiry_time(), Duration::from_secs(non_default_invoice_expiry_secs.into())); } @@ -1888,7 +1889,7 @@ mod test { .map(|route_hint| route_hint.phantom_scid) .collect::>(); - let invoice = crate::utils::create_phantom_invoice::<&test_utils::TestKeysInterface, + let invoice = create_phantom_invoice::<&test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestLogger>(invoice_amt, None, "test".to_string(), 3600, phantom_route_hints, invoice_node.keys_manager, invoice_node.keys_manager, invoice_node.logger, Currency::BitcoinTestnet, None, Duration::from_secs(1234567)).unwrap(); @@ -1896,7 +1897,7 @@ mod test { let invoice_hints = invoice.private_routes(); for hint in invoice_hints { - let hints = &(hint.0).0; + let hints = &hint.0; match hints.len() { 1 => { assert!(nodes_contains_public_channels); @@ -1921,7 +1922,7 @@ mod test { let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); let nodes = create_network(2, &node_cfgs, &node_chanmgrs); - let result = crate::utils::create_invoice_from_channelmanager_and_duration_since_epoch( + let result = create_invoice_from_channelmanager_and_duration_since_epoch( nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Currency::BitcoinTestnet, Some(10_000), "Some description".into(), Duration::from_secs(1234567), 3600, Some(MIN_FINAL_CLTV_EXPIRY_DELTA - 4), ); diff --git a/lightning/src/ln/mod.rs b/lightning/src/ln/mod.rs index 9bac19104bf..8009f31b0dc 100644 --- a/lightning/src/ln/mod.rs +++ b/lightning/src/ln/mod.rs @@ -25,6 +25,11 @@ pub mod features; pub mod script; pub mod types; +// TODO: These modules were moved from lightning-invoice and need to be better integrated into this +// crate now: +pub mod invoice_utils; +pub mod bolt11_payment; + pub use lightning_types::payment::{PaymentHash, PaymentPreimage, PaymentSecret}; #[cfg(fuzzing)] From 9c93bd56c25976ede81ffc44bc83e8b3adffc294 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Fri, 9 Aug 2024 02:45:55 +0000 Subject: [PATCH 09/11] Provide the signer with a full `RawBolt11Invoice` to sign Now that the `lightning` crate depends on the `lightning-invoice` crate, there's no reason to have the `sign_invoice` method take raw base32 field elements as we can now give it a real `RawBolt11Invoice`, which we do here. This simplifies the interface and avoids a serialization-deserialization roundtrip when signing invoices in a validating signer. FIxes #3227 --- fuzz/src/chanmon_consistency.rs | 5 +++-- fuzz/src/full_stack.rs | 5 +++-- fuzz/src/onion_message.rs | 5 +++-- lightning/src/ln/invoice_utils.rs | 13 ++++--------- lightning/src/sign/mod.rs | 24 +++++++++--------------- lightning/src/util/invoice.rs | 28 ---------------------------- lightning/src/util/mod.rs | 1 - lightning/src/util/test_utils.rs | 9 +++++---- 8 files changed, 27 insertions(+), 63 deletions(-) delete mode 100644 lightning/src/util/invoice.rs diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index 06d2f6c845f..4322fccb16f 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -71,6 +71,8 @@ use lightning::util::logger::Logger; use lightning::util::ser::{Readable, ReadableArgs, Writeable, Writer}; use lightning::util::test_channel_signer::{EnforcementState, TestChannelSigner}; +use lightning_invoice::RawBolt11Invoice; + use crate::utils::test_logger::{self, Output}; use crate::utils::test_persister::TestPersister; @@ -79,7 +81,6 @@ use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature}; use bitcoin::secp256k1::schnorr; use bitcoin::secp256k1::{self, Message, PublicKey, Scalar, Secp256k1, SecretKey}; -use bech32::u5; use std::cmp::{self, Ordering}; use std::io::Cursor; use std::mem; @@ -332,7 +333,7 @@ impl NodeSigner for KeyProvider { } fn sign_invoice( - &self, _hrp_bytes: &[u8], _invoice_data: &[u5], _recipient: Recipient, + &self, _invoice: &RawBolt11Invoice, _recipient: Recipient, ) -> Result { unreachable!() } diff --git a/fuzz/src/full_stack.rs b/fuzz/src/full_stack.rs index 55a96521700..fa0c2906294 100644 --- a/fuzz/src/full_stack.rs +++ b/fuzz/src/full_stack.rs @@ -68,6 +68,8 @@ use lightning::util::logger::Logger; use lightning::util::ser::{Readable, ReadableArgs, Writeable}; use lightning::util::test_channel_signer::{EnforcementState, TestChannelSigner}; +use lightning_invoice::RawBolt11Invoice; + use crate::utils::test_logger; use crate::utils::test_persister::TestPersister; @@ -76,7 +78,6 @@ use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature}; use bitcoin::secp256k1::schnorr; use bitcoin::secp256k1::{self, Message, PublicKey, Scalar, Secp256k1, SecretKey}; -use bech32::u5; use std::cell::RefCell; use std::cmp; use std::convert::TryInto; @@ -406,7 +407,7 @@ impl NodeSigner for KeyProvider { } fn sign_invoice( - &self, _hrp_bytes: &[u8], _invoice_data: &[u5], _recipient: Recipient, + &self, _invoice: &RawBolt11Invoice, _recipient: Recipient, ) -> Result { unreachable!() } diff --git a/fuzz/src/onion_message.rs b/fuzz/src/onion_message.rs index a05551410f3..48f184af709 100644 --- a/fuzz/src/onion_message.rs +++ b/fuzz/src/onion_message.rs @@ -1,5 +1,4 @@ // Imports that need to be added manually -use bech32::u5; use bitcoin::script::ScriptBuf; use bitcoin::secp256k1::ecdh::SharedSecret; use bitcoin::secp256k1::ecdsa::RecoverableSignature; @@ -27,6 +26,8 @@ use lightning::util::logger::Logger; use lightning::util::ser::{Readable, Writeable, Writer}; use lightning::util::test_channel_signer::TestChannelSigner; +use lightning_invoice::RawBolt11Invoice; + use crate::utils::test_logger; use std::io::{self, Cursor}; @@ -225,7 +226,7 @@ impl NodeSigner for KeyProvider { } fn sign_invoice( - &self, _hrp_bytes: &[u8], _invoice_data: &[u5], _recipient: Recipient, + &self, _invoice: &RawBolt11Invoice, _recipient: Recipient, ) -> Result { unreachable!() } diff --git a/lightning/src/ln/invoice_utils.rs b/lightning/src/ln/invoice_utils.rs index 5e4ba508f75..5a793052cc3 100644 --- a/lightning/src/ln/invoice_utils.rs +++ b/lightning/src/ln/invoice_utils.rs @@ -5,7 +5,6 @@ use lightning_invoice::{Description, Bolt11InvoiceDescription, Sha256}; use crate::prelude::*; -use bech32::ToBase32; use bitcoin::hashes::Hash; use crate::chain; use crate::chain::chaininterface::{BroadcasterInterface, FeeEstimator}; @@ -219,10 +218,8 @@ where Ok(inv) => inv, Err(e) => return Err(SignOrCreationError::CreationError(e)) }; - let hrp_str = raw_invoice.hrp.to_string(); - let hrp_bytes = hrp_str.as_bytes(); - let data_without_signature = raw_invoice.data.to_base32(); - let signed_raw_invoice = raw_invoice.sign(|_| node_signer.sign_invoice(hrp_bytes, &data_without_signature, Recipient::PhantomNode)); + let signature = node_signer.sign_invoice(&raw_invoice, Recipient::PhantomNode); + let signed_raw_invoice = raw_invoice.sign(|_| signature); match signed_raw_invoice { Ok(inv) => Ok(Bolt11Invoice::from_signed(inv).unwrap()), Err(e) => Err(SignOrCreationError::SignError(e)) @@ -571,10 +568,8 @@ fn _create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_has Ok(inv) => inv, Err(e) => return Err(SignOrCreationError::CreationError(e)) }; - let hrp_str = raw_invoice.hrp.to_string(); - let hrp_bytes = hrp_str.as_bytes(); - let data_without_signature = raw_invoice.data.to_base32(); - let signed_raw_invoice = raw_invoice.sign(|_| node_signer.sign_invoice(hrp_bytes, &data_without_signature, Recipient::Node)); + let signature = node_signer.sign_invoice(&raw_invoice, Recipient::Node); + let signed_raw_invoice = raw_invoice.sign(|_| signature); match signed_raw_invoice { Ok(inv) => Ok(Bolt11Invoice::from_signed(inv).unwrap()), Err(e) => Err(SignOrCreationError::SignError(e)) diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index f5fc9bb19bd..1dd8c90f65f 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -24,7 +24,6 @@ use bitcoin::sighash::EcdsaSighashType; use bitcoin::transaction::Version; use bitcoin::transaction::{Transaction, TxIn, TxOut}; -use bech32::u5; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hashes::sha256d::Hash as Sha256dHash; use bitcoin::hashes::{Hash, HashEngine}; @@ -37,6 +36,8 @@ use bitcoin::secp256k1::All; use bitcoin::secp256k1::{Keypair, PublicKey, Scalar, Secp256k1, SecretKey, Signing}; use bitcoin::{secp256k1, Psbt, Sequence, Txid, WPubkeyHash, Witness}; +use lightning_invoice::RawBolt11Invoice; + use crate::chain::transaction::OutPoint; use crate::crypto::utils::{hkdf_extract_expand_twice, sign, sign_with_aux_rand}; use crate::ln::chan_utils; @@ -69,7 +70,6 @@ use crate::sign::ecdsa::EcdsaChannelSigner; #[cfg(taproot)] use crate::sign::taproot::TaprootChannelSigner; use crate::util::atomic_counter::AtomicCounter; -use crate::util::invoice::construct_invoice_preimage; use core::convert::TryInto; use core::ops::Deref; use core::sync::atomic::{AtomicUsize, Ordering}; @@ -867,7 +867,7 @@ pub trait NodeSigner { /// /// Errors if the [`Recipient`] variant is not supported by the implementation. fn sign_invoice( - &self, hrp_bytes: &[u8], invoice_data: &[u5], recipient: Recipient, + &self, invoice: &RawBolt11Invoice, recipient: Recipient, ) -> Result; /// Signs the [`TaggedHash`] of a BOLT 12 invoice request. @@ -2174,17 +2174,14 @@ impl NodeSigner for KeysManager { } fn sign_invoice( - &self, hrp_bytes: &[u8], invoice_data: &[u5], recipient: Recipient, + &self, invoice: &RawBolt11Invoice, recipient: Recipient, ) -> Result { - let preimage = construct_invoice_preimage(&hrp_bytes, &invoice_data); + let hash = invoice.signable_hash(); let secret = match recipient { Recipient::Node => Ok(&self.node_secret), Recipient::PhantomNode => Err(()), }?; - Ok(self.secp_ctx.sign_ecdsa_recoverable( - &hash_to_message!(&Sha256::hash(&preimage).to_byte_array()), - secret, - )) + Ok(self.secp_ctx.sign_ecdsa_recoverable(&hash_to_message!(&hash), secret)) } fn sign_bolt12_invoice_request( @@ -2352,17 +2349,14 @@ impl NodeSigner for PhantomKeysManager { } fn sign_invoice( - &self, hrp_bytes: &[u8], invoice_data: &[u5], recipient: Recipient, + &self, invoice: &RawBolt11Invoice, recipient: Recipient, ) -> Result { - let preimage = construct_invoice_preimage(&hrp_bytes, &invoice_data); + let hash = invoice.signable_hash(); let secret = match recipient { Recipient::Node => &self.inner.node_secret, Recipient::PhantomNode => &self.phantom_secret, }; - Ok(self.inner.secp_ctx.sign_ecdsa_recoverable( - &hash_to_message!(&Sha256::hash(&preimage).to_byte_array()), - secret, - )) + Ok(self.inner.secp_ctx.sign_ecdsa_recoverable(&hash_to_message!(&hash), secret)) } fn sign_bolt12_invoice_request( diff --git a/lightning/src/util/invoice.rs b/lightning/src/util/invoice.rs deleted file mode 100644 index 4a4baa45267..00000000000 --- a/lightning/src/util/invoice.rs +++ /dev/null @@ -1,28 +0,0 @@ -//! Low level invoice utilities. - -use bech32::{u5, FromBase32}; - -#[allow(unused)] -use crate::prelude::*; - -/// Construct the invoice's HRP and signatureless data into a preimage to be hashed. -pub fn construct_invoice_preimage(hrp_bytes: &[u8], data_without_signature: &[u5]) -> Vec { - let mut preimage = Vec::::from(hrp_bytes); - - let mut data_part = Vec::from(data_without_signature); - let overhang = (data_part.len() * 5) % 8; - if overhang > 0 { - // add padding if data does not end at a byte boundary - data_part.push(u5::try_from_u8(0).unwrap()); - - // if overhang is in (1..3) we need to add u5(0) padding two times - if overhang < 3 { - data_part.push(u5::try_from_u8(0).unwrap()); - } - } - - preimage.extend_from_slice(&Vec::::from_base32(&data_part) - .expect("No padding error may occur due to appended zero above.")); - preimage -} - diff --git a/lightning/src/util/mod.rs b/lightning/src/util/mod.rs index cfcea837971..f19a9b8d8aa 100644 --- a/lightning/src/util/mod.rs +++ b/lightning/src/util/mod.rs @@ -18,7 +18,6 @@ pub mod ser_macros; pub mod errors; pub mod ser; pub mod message_signing; -pub mod invoice; pub mod persist; pub mod scid_utils; pub mod sweep; diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index 113006cbb1c..f5256064edf 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -65,6 +65,8 @@ use bitcoin::secp256k1::ecdh::SharedSecret; use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature}; use bitcoin::secp256k1::schnorr; +use lightning_invoice::RawBolt11Invoice; + use crate::io; use crate::prelude::*; use core::cell::RefCell; @@ -72,7 +74,6 @@ use core::time::Duration; use crate::sync::{Mutex, Arc}; use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use core::mem; -use bech32::u5; use crate::sign::{InMemorySigner, RandomBytes, Recipient, EntropySource, NodeSigner, SignerProvider}; #[cfg(feature = "std")] @@ -1217,7 +1218,7 @@ impl NodeSigner for TestNodeSigner { Ok(SharedSecret::new(other_key, &node_secret)) } - fn sign_invoice(&self, _: &[u8], _: &[bech32::u5], _: Recipient) -> Result { + fn sign_invoice(&self, _: &RawBolt11Invoice, _: Recipient) -> Result { unreachable!() } @@ -1270,8 +1271,8 @@ impl NodeSigner for TestKeysInterface { self.backing.get_inbound_payment_key_material() } - fn sign_invoice(&self, hrp_bytes: &[u8], invoice_data: &[u5], recipient: Recipient) -> Result { - self.backing.sign_invoice(hrp_bytes, invoice_data, recipient) + fn sign_invoice(&self, invoice: &RawBolt11Invoice, recipient: Recipient) -> Result { + self.backing.sign_invoice(invoice, recipient) } fn sign_bolt12_invoice_request( From 30879ed0350257ecf0281e8fb617abfb26cd762d Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Fri, 9 Aug 2024 15:42:48 +0000 Subject: [PATCH 10/11] Prepare to `rustfmt` newly added files In the next commit we'll `rustfmt` newly-added files, but before we do so we clean up some code so that the resulting files won't be quite as absurd. We also exclude the new `invoice_utils.rs` file, as it needs quite substantial cleanups. --- lightning/src/ln/bolt11_payment.rs | 7 +++---- rustfmt_excluded_files | 1 + 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lightning/src/ln/bolt11_payment.rs b/lightning/src/ln/bolt11_payment.rs index 5c5e3711abd..d590b209b9f 100644 --- a/lightning/src/ln/bolt11_payment.rs +++ b/lightning/src/ln/bolt11_payment.rs @@ -186,6 +186,8 @@ mod tests { let (payment_hash, payment_secret) = nodes[1].node.create_inbound_payment(None, 7200, None).unwrap(); + let secp_ctx = Secp256k1::new(); + let node_secret = nodes[1].keys_manager.backing.get_node_secret_key(); let invoice = InvoiceBuilder::new(Currency::Bitcoin) .description("test".into()) .payment_hash(Sha256::from_slice(&payment_hash.0).unwrap()) @@ -194,10 +196,7 @@ mod tests { .min_final_cltv_expiry_delta(144) .amount_milli_satoshis(50_000) .payment_metadata(payment_metadata.clone()) - .build_signed(|hash| { - Secp256k1::new().sign_ecdsa_recoverable(hash, - &nodes[1].keys_manager.backing.get_node_secret_key()) - }) + .build_signed(|hash| secp_ctx.sign_ecdsa_recoverable(hash, &node_secret)) .unwrap(); let (hash, onion, params) = payment_parameters_from_invoice(&invoice).unwrap(); diff --git a/rustfmt_excluded_files b/rustfmt_excluded_files index 58f77b4a91b..898a2c26e8a 100644 --- a/rustfmt_excluded_files +++ b/rustfmt_excluded_files @@ -41,6 +41,7 @@ ./lightning/src/ln/functional_test_utils.rs ./lightning/src/ln/functional_tests.rs ./lightning/src/ln/inbound_payment.rs +./lightning/src/ln/invoice_utils.rs ./lightning/src/ln/max_payment_path_len_tests.rs ./lightning/src/ln/mod.rs ./lightning/src/ln/monitor_tests.rs From ae59d1dfb20a8036eaa792ea96d644bb519e7e84 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Fri, 9 Aug 2024 15:27:38 +0000 Subject: [PATCH 11/11] `rustfmt` new files added in the past few commits The past handful of commits were mostly moving code around, so to aid reviewers violated our `rustfmt` rules. Here we rectify that by `rustfmt`'ing the newly-added files. --- lightning-types/src/features.rs | 427 ++++++++++++++++++++--------- lightning-types/src/payment.rs | 9 +- lightning/src/ln/bolt11_payment.rs | 57 ++-- 3 files changed, 325 insertions(+), 168 deletions(-) diff --git a/lightning-types/src/features.rs b/lightning-types/src/features.rs index 613bfcf8d70..3aec5358d2b 100644 --- a/lightning-types/src/features.rs +++ b/lightning-types/src/features.rs @@ -77,15 +77,15 @@ //! //! [BOLT #9]: https://github.com/lightning/bolts/blob/master/09-features.md -use core::{cmp, fmt}; use core::borrow::Borrow; use core::hash::{Hash, Hasher}; use core::marker::PhantomData; +use core::{cmp, fmt}; -use alloc::vec::Vec; use alloc::vec; +use alloc::vec::Vec; -use bech32::{Base32Len, FromBase32, ToBase32, u5, WriteBase32}; +use bech32::{u5, Base32Len, FromBase32, ToBase32, WriteBase32}; mod sealed { use super::*; @@ -135,42 +135,48 @@ mod sealed { }; } - define_context!(InitContext, [ - // Byte 0 - DataLossProtect | InitialRoutingSync | UpfrontShutdownScript | GossipQueries, - // Byte 1 - VariableLengthOnion | StaticRemoteKey | PaymentSecret, - // Byte 2 - BasicMPP | Wumbo | AnchorsNonzeroFeeHtlcTx | AnchorsZeroFeeHtlcTx, - // Byte 3 - RouteBlinding | ShutdownAnySegwit | Taproot, - // Byte 4 - OnionMessages, - // Byte 5 - ChannelType | SCIDPrivacy, - // Byte 6 - ZeroConf, - // Byte 7 - Trampoline, - ]); - define_context!(NodeContext, [ - // Byte 0 - DataLossProtect | UpfrontShutdownScript | GossipQueries, - // Byte 1 - VariableLengthOnion | StaticRemoteKey | PaymentSecret, - // Byte 2 - BasicMPP | Wumbo | AnchorsNonzeroFeeHtlcTx | AnchorsZeroFeeHtlcTx, - // Byte 3 - RouteBlinding | ShutdownAnySegwit | Taproot, - // Byte 4 - OnionMessages, - // Byte 5 - ChannelType | SCIDPrivacy, - // Byte 6 - ZeroConf | Keysend, - // Byte 7 - Trampoline, - ]); + define_context!( + InitContext, + [ + // Byte 0 + DataLossProtect | InitialRoutingSync | UpfrontShutdownScript | GossipQueries, + // Byte 1 + VariableLengthOnion | StaticRemoteKey | PaymentSecret, + // Byte 2 + BasicMPP | Wumbo | AnchorsNonzeroFeeHtlcTx | AnchorsZeroFeeHtlcTx, + // Byte 3 + RouteBlinding | ShutdownAnySegwit | Taproot, + // Byte 4 + OnionMessages, + // Byte 5 + ChannelType | SCIDPrivacy, + // Byte 6 + ZeroConf, + // Byte 7 + Trampoline, + ] + ); + define_context!( + NodeContext, + [ + // Byte 0 + DataLossProtect | UpfrontShutdownScript | GossipQueries, + // Byte 1 + VariableLengthOnion | StaticRemoteKey | PaymentSecret, + // Byte 2 + BasicMPP | Wumbo | AnchorsNonzeroFeeHtlcTx | AnchorsZeroFeeHtlcTx, + // Byte 3 + RouteBlinding | ShutdownAnySegwit | Taproot, + // Byte 4 + OnionMessages, + // Byte 5 + ChannelType | SCIDPrivacy, + // Byte 6 + ZeroConf | Keysend, + // Byte 7 + Trampoline, + ] + ); define_context!(ChannelContext, []); define_context!(Bolt11InvoiceContext, [ // Byte 0 @@ -361,79 +367,223 @@ mod sealed { } } - define_feature!(1, DataLossProtect, [InitContext, NodeContext], - "Feature flags for `option_data_loss_protect`.", set_data_loss_protect_optional, - set_data_loss_protect_required, supports_data_loss_protect, requires_data_loss_protect); + define_feature!( + 1, + DataLossProtect, + [InitContext, NodeContext], + "Feature flags for `option_data_loss_protect`.", + set_data_loss_protect_optional, + set_data_loss_protect_required, + supports_data_loss_protect, + requires_data_loss_protect + ); // NOTE: Per Bolt #9, initial_routing_sync has no even bit. - define_feature!(3, InitialRoutingSync, [InitContext], "Feature flags for `initial_routing_sync`.", - set_initial_routing_sync_optional, set_initial_routing_sync_required, - initial_routing_sync); - define_feature!(5, UpfrontShutdownScript, [InitContext, NodeContext], - "Feature flags for `option_upfront_shutdown_script`.", set_upfront_shutdown_script_optional, - set_upfront_shutdown_script_required, supports_upfront_shutdown_script, - requires_upfront_shutdown_script); - define_feature!(7, GossipQueries, [InitContext, NodeContext], - "Feature flags for `gossip_queries`.", set_gossip_queries_optional, set_gossip_queries_required, - supports_gossip_queries, requires_gossip_queries); - define_feature!(9, VariableLengthOnion, [InitContext, NodeContext, Bolt11InvoiceContext], - "Feature flags for `var_onion_optin`.", set_variable_length_onion_optional, - set_variable_length_onion_required, supports_variable_length_onion, - requires_variable_length_onion); - define_feature!(13, StaticRemoteKey, [InitContext, NodeContext, ChannelTypeContext], - "Feature flags for `option_static_remotekey`.", set_static_remote_key_optional, - set_static_remote_key_required, supports_static_remote_key, requires_static_remote_key); - define_feature!(15, PaymentSecret, [InitContext, NodeContext, Bolt11InvoiceContext], - "Feature flags for `payment_secret`.", set_payment_secret_optional, set_payment_secret_required, - supports_payment_secret, requires_payment_secret); - define_feature!(17, BasicMPP, [InitContext, NodeContext, Bolt11InvoiceContext, Bolt12InvoiceContext], - "Feature flags for `basic_mpp`.", set_basic_mpp_optional, set_basic_mpp_required, - supports_basic_mpp, requires_basic_mpp); - define_feature!(19, Wumbo, [InitContext, NodeContext], - "Feature flags for `option_support_large_channel` (aka wumbo channels).", set_wumbo_optional, set_wumbo_required, - supports_wumbo, requires_wumbo); - define_feature!(21, AnchorsNonzeroFeeHtlcTx, [InitContext, NodeContext, ChannelTypeContext], - "Feature flags for `option_anchors_nonzero_fee_htlc_tx`.", set_anchors_nonzero_fee_htlc_tx_optional, - set_anchors_nonzero_fee_htlc_tx_required, supports_anchors_nonzero_fee_htlc_tx, requires_anchors_nonzero_fee_htlc_tx); - define_feature!(23, AnchorsZeroFeeHtlcTx, [InitContext, NodeContext, ChannelTypeContext], - "Feature flags for `option_anchors_zero_fee_htlc_tx`.", set_anchors_zero_fee_htlc_tx_optional, - set_anchors_zero_fee_htlc_tx_required, supports_anchors_zero_fee_htlc_tx, requires_anchors_zero_fee_htlc_tx); - define_feature!(25, RouteBlinding, [InitContext, NodeContext], - "Feature flags for `option_route_blinding`.", set_route_blinding_optional, - set_route_blinding_required, supports_route_blinding, requires_route_blinding); - define_feature!(27, ShutdownAnySegwit, [InitContext, NodeContext], - "Feature flags for `opt_shutdown_anysegwit`.", set_shutdown_any_segwit_optional, - set_shutdown_any_segwit_required, supports_shutdown_anysegwit, requires_shutdown_anysegwit); - define_feature!(31, Taproot, [InitContext, NodeContext, ChannelTypeContext], - "Feature flags for `option_taproot`.", set_taproot_optional, - set_taproot_required, supports_taproot, requires_taproot); - define_feature!(39, OnionMessages, [InitContext, NodeContext], - "Feature flags for `option_onion_messages`.", set_onion_messages_optional, - set_onion_messages_required, supports_onion_messages, requires_onion_messages); - define_feature!(45, ChannelType, [InitContext, NodeContext], - "Feature flags for `option_channel_type`.", set_channel_type_optional, - set_channel_type_required, supports_channel_type, requires_channel_type); + define_feature!( + 3, + InitialRoutingSync, + [InitContext], + "Feature flags for `initial_routing_sync`.", + set_initial_routing_sync_optional, + set_initial_routing_sync_required, + initial_routing_sync + ); + define_feature!( + 5, + UpfrontShutdownScript, + [InitContext, NodeContext], + "Feature flags for `option_upfront_shutdown_script`.", + set_upfront_shutdown_script_optional, + set_upfront_shutdown_script_required, + supports_upfront_shutdown_script, + requires_upfront_shutdown_script + ); + define_feature!( + 7, + GossipQueries, + [InitContext, NodeContext], + "Feature flags for `gossip_queries`.", + set_gossip_queries_optional, + set_gossip_queries_required, + supports_gossip_queries, + requires_gossip_queries + ); + define_feature!( + 9, + VariableLengthOnion, + [InitContext, NodeContext, Bolt11InvoiceContext], + "Feature flags for `var_onion_optin`.", + set_variable_length_onion_optional, + set_variable_length_onion_required, + supports_variable_length_onion, + requires_variable_length_onion + ); + define_feature!( + 13, + StaticRemoteKey, + [InitContext, NodeContext, ChannelTypeContext], + "Feature flags for `option_static_remotekey`.", + set_static_remote_key_optional, + set_static_remote_key_required, + supports_static_remote_key, + requires_static_remote_key + ); + define_feature!( + 15, + PaymentSecret, + [InitContext, NodeContext, Bolt11InvoiceContext], + "Feature flags for `payment_secret`.", + set_payment_secret_optional, + set_payment_secret_required, + supports_payment_secret, + requires_payment_secret + ); + define_feature!( + 17, + BasicMPP, + [InitContext, NodeContext, Bolt11InvoiceContext, Bolt12InvoiceContext], + "Feature flags for `basic_mpp`.", + set_basic_mpp_optional, + set_basic_mpp_required, + supports_basic_mpp, + requires_basic_mpp + ); + define_feature!( + 19, + Wumbo, + [InitContext, NodeContext], + "Feature flags for `option_support_large_channel` (aka wumbo channels).", + set_wumbo_optional, + set_wumbo_required, + supports_wumbo, + requires_wumbo + ); + define_feature!( + 21, + AnchorsNonzeroFeeHtlcTx, + [InitContext, NodeContext, ChannelTypeContext], + "Feature flags for `option_anchors_nonzero_fee_htlc_tx`.", + set_anchors_nonzero_fee_htlc_tx_optional, + set_anchors_nonzero_fee_htlc_tx_required, + supports_anchors_nonzero_fee_htlc_tx, + requires_anchors_nonzero_fee_htlc_tx + ); + define_feature!( + 23, + AnchorsZeroFeeHtlcTx, + [InitContext, NodeContext, ChannelTypeContext], + "Feature flags for `option_anchors_zero_fee_htlc_tx`.", + set_anchors_zero_fee_htlc_tx_optional, + set_anchors_zero_fee_htlc_tx_required, + supports_anchors_zero_fee_htlc_tx, + requires_anchors_zero_fee_htlc_tx + ); + define_feature!( + 25, + RouteBlinding, + [InitContext, NodeContext], + "Feature flags for `option_route_blinding`.", + set_route_blinding_optional, + set_route_blinding_required, + supports_route_blinding, + requires_route_blinding + ); + define_feature!( + 27, + ShutdownAnySegwit, + [InitContext, NodeContext], + "Feature flags for `opt_shutdown_anysegwit`.", + set_shutdown_any_segwit_optional, + set_shutdown_any_segwit_required, + supports_shutdown_anysegwit, + requires_shutdown_anysegwit + ); + define_feature!( + 31, + Taproot, + [InitContext, NodeContext, ChannelTypeContext], + "Feature flags for `option_taproot`.", + set_taproot_optional, + set_taproot_required, + supports_taproot, + requires_taproot + ); + define_feature!( + 39, + OnionMessages, + [InitContext, NodeContext], + "Feature flags for `option_onion_messages`.", + set_onion_messages_optional, + set_onion_messages_required, + supports_onion_messages, + requires_onion_messages + ); + define_feature!( + 45, + ChannelType, + [InitContext, NodeContext], + "Feature flags for `option_channel_type`.", + set_channel_type_optional, + set_channel_type_required, + supports_channel_type, + requires_channel_type + ); define_feature!(47, SCIDPrivacy, [InitContext, NodeContext, ChannelTypeContext], "Feature flags for only forwarding with SCID aliasing. Called `option_scid_alias` in the BOLTs", set_scid_privacy_optional, set_scid_privacy_required, supports_scid_privacy, requires_scid_privacy); - define_feature!(49, PaymentMetadata, [Bolt11InvoiceContext], - "Feature flags for payment metadata in invoices.", set_payment_metadata_optional, - set_payment_metadata_required, supports_payment_metadata, requires_payment_metadata); + define_feature!( + 49, + PaymentMetadata, + [Bolt11InvoiceContext], + "Feature flags for payment metadata in invoices.", + set_payment_metadata_optional, + set_payment_metadata_required, + supports_payment_metadata, + requires_payment_metadata + ); define_feature!(51, ZeroConf, [InitContext, NodeContext, ChannelTypeContext], "Feature flags for accepting channels with zero confirmations. Called `option_zeroconf` in the BOLTs", set_zero_conf_optional, set_zero_conf_required, supports_zero_conf, requires_zero_conf); - define_feature!(55, Keysend, [NodeContext], - "Feature flags for keysend payments.", set_keysend_optional, set_keysend_required, - supports_keysend, requires_keysend); - define_feature!(57, Trampoline, [InitContext, NodeContext, Bolt11InvoiceContext], - "Feature flags for Trampoline routing.", set_trampoline_routing_optional, set_trampoline_routing_required, - supports_trampoline_routing, requires_trampoline_routing); + define_feature!( + 55, + Keysend, + [NodeContext], + "Feature flags for keysend payments.", + set_keysend_optional, + set_keysend_required, + supports_keysend, + requires_keysend + ); + define_feature!( + 57, + Trampoline, + [InitContext, NodeContext, Bolt11InvoiceContext], + "Feature flags for Trampoline routing.", + set_trampoline_routing_optional, + set_trampoline_routing_required, + supports_trampoline_routing, + requires_trampoline_routing + ); // Note: update the module-level docs when a new feature bit is added! #[cfg(any(test, feature = "_test_utils"))] - define_feature!(123456789, UnknownFeature, - [NodeContext, ChannelContext, Bolt11InvoiceContext, OfferContext, InvoiceRequestContext, Bolt12InvoiceContext, BlindedHopContext], - "Feature flags for an unknown feature used in testing.", set_unknown_feature_optional, - set_unknown_feature_required, supports_unknown_test_feature, requires_unknown_test_feature); + define_feature!( + 123456789, + UnknownFeature, + [ + NodeContext, + ChannelContext, + Bolt11InvoiceContext, + OfferContext, + InvoiceRequestContext, + Bolt12InvoiceContext, + BlindedHopContext + ], + "Feature flags for an unknown feature used in testing.", + set_unknown_feature_optional, + set_unknown_feature_required, + supports_unknown_test_feature, + requires_unknown_test_feature + ); } const ANY_REQUIRED_FEATURES_MASK: u8 = 0b01_01_01_01; @@ -471,10 +621,7 @@ impl core::ops::BitOr for Features { impl Clone for Features { fn clone(&self) -> Self { - Self { - flags: self.flags.clone(), - mark: PhantomData, - } + Self { flags: self.flags.clone(), mark: PhantomData } } } impl Hash for Features { @@ -492,8 +639,16 @@ impl PartialEq for Features { let mut self_iter = self.flags.iter(); loop { match (o_iter.next(), self_iter.next()) { - (Some(o), Some(us)) => if o != us { return false }, - (Some(b), None) | (None, Some(b)) => if *b != 0 { return false }, + (Some(o), Some(us)) => { + if o != us { + return false; + } + }, + (Some(b), None) | (None, Some(b)) => { + if *b != 0 { + return false; + } + }, (None, None) => return true, } } @@ -613,7 +768,9 @@ impl ChannelTypeFeatures { pub fn anchors_zero_htlc_fee_and_dependencies() -> Self { let mut ret = Self::empty(); ::set_required_bit(&mut ret.flags); - ::set_required_bit(&mut ret.flags); + ::set_required_bit( + &mut ret.flags, + ); ret } } @@ -630,14 +787,19 @@ impl ToBase32 for Bolt11InvoiceFeatures { let new_bit_pos = bit_pos_from_left_0_indexed % 5; let shifted_chunk_u16 = (*byte as u16) << new_bit_pos; let curr_u5_as_u8 = res_u5s[new_u5_idx].to_u8(); - res_u5s[new_u5_idx] = u5::try_from_u8(curr_u5_as_u8 | ((shifted_chunk_u16 & 0x001f) as u8)).unwrap(); + res_u5s[new_u5_idx] = + u5::try_from_u8(curr_u5_as_u8 | ((shifted_chunk_u16 & 0x001f) as u8)).unwrap(); if new_u5_idx > 0 { let curr_u5_as_u8 = res_u5s[new_u5_idx - 1].to_u8(); - res_u5s[new_u5_idx - 1] = u5::try_from_u8(curr_u5_as_u8 | (((shifted_chunk_u16 >> 5) & 0x001f) as u8)).unwrap(); + res_u5s[new_u5_idx - 1] = + u5::try_from_u8(curr_u5_as_u8 | (((shifted_chunk_u16 >> 5) & 0x001f) as u8)) + .unwrap(); } if new_u5_idx > 1 { let curr_u5_as_u8 = res_u5s[new_u5_idx - 2].to_u8(); - res_u5s[new_u5_idx - 2] = u5::try_from_u8(curr_u5_as_u8 | (((shifted_chunk_u16 >> 10) & 0x001f) as u8)).unwrap(); + res_u5s[new_u5_idx - 2] = + u5::try_from_u8(curr_u5_as_u8 | (((shifted_chunk_u16 >> 10) & 0x001f) as u8)) + .unwrap(); } } // Trim the highest feature bits. @@ -669,7 +831,7 @@ impl FromBase32 for Bolt11InvoiceFeatures { let chunk_u16 = chunk.to_u8() as u16; res_bytes[new_byte_idx] |= ((chunk_u16 << new_bit_pos) & 0xff) as u8; if new_byte_idx != length_bytes - 1 { - res_bytes[new_byte_idx + 1] |= ((chunk_u16 >> (8-new_bit_pos)) & 0xff) as u8; + res_bytes[new_byte_idx + 1] |= ((chunk_u16 >> (8 - new_bit_pos)) & 0xff) as u8; } } // Trim the highest feature bits. @@ -683,10 +845,7 @@ impl FromBase32 for Bolt11InvoiceFeatures { impl Features { /// Create a blank Features with no features set pub fn empty() -> Self { - Features { - flags: Vec::new(), - mark: PhantomData, - } + Features { flags: Vec::new(), mark: PhantomData } } /// Converts `Features` to `Features`. Only known `T` features relevant to context `C` are @@ -702,7 +861,7 @@ impl Features { flags.push(byte & from_known_features & to_known_features); } } - Features:: { flags, mark: PhantomData, } + Features:: { flags, mark: PhantomData } } /// Create a Features given a set of flags, in little-endian. This is in reverse byte order from @@ -710,10 +869,7 @@ impl Features { /// /// This is not exported to bindings users as we don't support export across multiple T pub fn from_le_bytes(flags: Vec) -> Features { - Features { - flags, - mark: PhantomData, - } + Features { flags, mark: PhantomData } } /// Returns the feature set as a list of bytes, in little-endian. This is in reverse byte order @@ -728,10 +884,7 @@ impl Features { /// This is not exported to bindings users as we don't support export across multiple T pub fn from_be_bytes(mut flags: Vec) -> Features { flags.reverse(); // Swap to little-endian - Self { - flags, - mark: PhantomData, - } + Self { flags, mark: PhantomData } } /// Returns true if this `Features` has any optional flags set @@ -799,11 +952,8 @@ impl Features { // both required and optional unknown features. let byte_count = T::KNOWN_FEATURE_MASK.len(); self.flags.iter().enumerate().any(|(i, &byte)| { - let unknown_features = if i < byte_count { - !T::KNOWN_FEATURE_MASK[i] - } else { - 0b11_11_11_11 - }; + let unknown_features = + if i < byte_count { !T::KNOWN_FEATURE_MASK[i] } else { 0b11_11_11_11 }; (byte & unknown_features) != 0 }) } @@ -940,7 +1090,9 @@ impl Features { } } -pub(crate) fn unset_features_mask_at_position(other: &Features, index: usize) -> u8 { +pub(crate) fn unset_features_mask_at_position( + other: &Features, index: usize, +) -> u8 { if index < other.flags.len() { // Form a mask similar to !T::KNOWN_FEATURE_MASK only for `other` !(other.flags[index] @@ -978,7 +1130,10 @@ mod tests { features.set_custom_bit(123456786).unwrap(); assert!(features.requires_unknown_bits()); assert!(features.supports_unknown_bits()); - assert_eq!(features.required_unknown_bits_from(&ChannelFeatures::empty()), vec![123456786, 123456788]); + assert_eq!( + features.required_unknown_bits_from(&ChannelFeatures::empty()), + vec![123456786, 123456788] + ); let mut limiter = ChannelFeatures::empty(); limiter.set_unknown_feature_optional(); @@ -1079,8 +1234,10 @@ mod tests { #[test] fn convert_to_context_with_unknown_flags() { // Ensure the `from` context has fewer known feature bytes than the `to` context. - assert!(::KNOWN_FEATURE_MASK.len() < - ::KNOWN_FEATURE_MASK.len()); + assert!( + ::KNOWN_FEATURE_MASK.len() + < ::KNOWN_FEATURE_MASK.len() + ); let mut channel_features = ChannelFeatures::empty(); channel_features.set_unknown_feature_optional(); assert!(channel_features.supports_unknown_bits()); diff --git a/lightning-types/src/payment.rs b/lightning-types/src/payment.rs index 1c36f332d1b..6b8854ac5f8 100644 --- a/lightning-types/src/payment.rs +++ b/lightning-types/src/payment.rs @@ -13,10 +13,7 @@ use alloc::vec::Vec; use core::borrow::Borrow; -use bitcoin::hashes::{ - Hash as _, - sha256::Hash as Sha256, -}; +use bitcoin::hashes::{sha256::Hash as Sha256, Hash as _}; // TODO: Once we switch to rust-bitcoin 0.32, import this as bitcoin::hex use hex_conservative::display::impl_fmt_traits; @@ -85,14 +82,14 @@ impl_fmt_traits! { } } -use bech32::{Base32Len, FromBase32, ToBase32, WriteBase32, u5}; +use bech32::{u5, Base32Len, FromBase32, ToBase32, WriteBase32}; impl FromBase32 for PaymentSecret { type Err = bech32::Error; fn from_base32(field_data: &[u5]) -> Result { if field_data.len() != 52 { - return Err(bech32::Error::InvalidLength) + return Err(bech32::Error::InvalidLength); } else { let data_bytes = Vec::::from_base32(field_data)?; let mut payment_secret = [0; 32]; diff --git a/lightning/src/ln/bolt11_payment.rs b/lightning/src/ln/bolt11_payment.rs index d590b209b9f..596ee49aca1 100644 --- a/lightning/src/ln/bolt11_payment.rs +++ b/lightning/src/ln/bolt11_payment.rs @@ -28,8 +28,9 @@ use crate::routing::router::{PaymentParameters, RouteParameters}; /// /// [`ChannelManager::send_payment`]: crate::ln::channelmanager::ChannelManager::send_payment /// [`ChannelManager::send_preflight_probes`]: crate::ln::channelmanager::ChannelManager::send_preflight_probes -pub fn payment_parameters_from_zero_amount_invoice(invoice: &Bolt11Invoice, amount_msat: u64) --> Result<(PaymentHash, RecipientOnionFields, RouteParameters), ()> { +pub fn payment_parameters_from_zero_amount_invoice( + invoice: &Bolt11Invoice, amount_msat: u64, +) -> Result<(PaymentHash, RecipientOnionFields, RouteParameters), ()> { if invoice.amount_milli_satoshis().is_some() { Err(()) } else { @@ -48,8 +49,9 @@ pub fn payment_parameters_from_zero_amount_invoice(invoice: &Bolt11Invoice, amou /// /// [`ChannelManager::send_payment`]: crate::ln::channelmanager::ChannelManager::send_payment /// [`ChannelManager::send_preflight_probes`]: crate::ln::channelmanager::ChannelManager::send_preflight_probes -pub fn payment_parameters_from_invoice(invoice: &Bolt11Invoice) --> Result<(PaymentHash, RecipientOnionFields, RouteParameters), ()> { +pub fn payment_parameters_from_invoice( + invoice: &Bolt11Invoice, +) -> Result<(PaymentHash, RecipientOnionFields, RouteParameters), ()> { if let Some(amount_msat) = invoice.amount_milli_satoshis() { Ok(params_from_invoice(invoice, amount_msat)) } else { @@ -57,18 +59,20 @@ pub fn payment_parameters_from_invoice(invoice: &Bolt11Invoice) } } -fn params_from_invoice(invoice: &Bolt11Invoice, amount_msat: u64) --> (PaymentHash, RecipientOnionFields, RouteParameters) { +fn params_from_invoice( + invoice: &Bolt11Invoice, amount_msat: u64, +) -> (PaymentHash, RecipientOnionFields, RouteParameters) { let payment_hash = PaymentHash((*invoice.payment_hash()).to_byte_array()); let mut recipient_onion = RecipientOnionFields::secret_only(*invoice.payment_secret()); recipient_onion.payment_metadata = invoice.payment_metadata().map(|v| v.clone()); let mut payment_params = PaymentParameters::from_node_id( - invoice.recover_payee_pub_key(), - invoice.min_final_cltv_expiry_delta() as u32 - ) - .with_route_hints(invoice.route_hints()).unwrap(); + invoice.recover_payee_pub_key(), + invoice.min_final_cltv_expiry_delta() as u32, + ) + .with_route_hints(invoice.route_hints()) + .unwrap(); if let Some(expiry) = invoice.expires_at() { payment_params = payment_params.with_expiry_time(expiry.as_secs()); } @@ -83,19 +87,18 @@ fn params_from_invoice(invoice: &Bolt11Invoice, amount_msat: u64) #[cfg(test)] mod tests { use super::*; - use lightning_invoice::{InvoiceBuilder, Currency}; - use bitcoin::hashes::sha256::Hash as Sha256; use crate::ln::types::PaymentSecret; use crate::routing::router::Payee; - use bitcoin::secp256k1::{SecretKey, PublicKey, Secp256k1}; + use bitcoin::hashes::sha256::Hash as Sha256; + use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; use core::time::Duration; + use lightning_invoice::{Currency, InvoiceBuilder}; #[cfg(feature = "std")] use std::time::SystemTime; fn duration_since_epoch() -> Duration { #[cfg(feature = "std")] - let duration_since_epoch = - SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); + let duration_since_epoch = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); #[cfg(not(feature = "std"))] let duration_since_epoch = Duration::from_secs(1234567); duration_since_epoch @@ -115,9 +118,7 @@ mod tests { .duration_since_epoch(duration_since_epoch()) .min_final_cltv_expiry_delta(144) .amount_milli_satoshis(128) - .build_signed(|hash| { - secp_ctx.sign_ecdsa_recoverable(hash, &private_key) - }) + .build_signed(|hash| secp_ctx.sign_ecdsa_recoverable(hash, &private_key)) .unwrap(); assert!(payment_parameters_from_zero_amount_invoice(&invoice, 42).is_err()); @@ -147,14 +148,13 @@ mod tests { .payment_secret(PaymentSecret([0; 32])) .duration_since_epoch(duration_since_epoch()) .min_final_cltv_expiry_delta(144) - .build_signed(|hash| { - secp_ctx.sign_ecdsa_recoverable(hash, &private_key) - }) - .unwrap(); + .build_signed(|hash| secp_ctx.sign_ecdsa_recoverable(hash, &private_key)) + .unwrap(); assert!(payment_parameters_from_invoice(&invoice).is_err()); - let (hash, onion, params) = payment_parameters_from_zero_amount_invoice(&invoice, 42).unwrap(); + let (hash, onion, params) = + payment_parameters_from_zero_amount_invoice(&invoice, 42).unwrap(); assert_eq!(&hash.0[..], &payment_hash[..]); assert_eq!(onion.payment_secret, Some(PaymentSecret([0; 32]))); assert_eq!(params.final_value_msat, 42); @@ -170,9 +170,9 @@ mod tests { #[cfg(feature = "std")] fn payment_metadata_end_to_end() { use crate::events::Event; - use crate::ln::channelmanager::{Retry, PaymentId}; - use crate::ln::msgs::ChannelMessageHandler; + use crate::ln::channelmanager::{PaymentId, Retry}; use crate::ln::functional_test_utils::*; + use crate::ln::msgs::ChannelMessageHandler; // Test that a payment metadata read from an invoice passed to `pay_invoice` makes it all // the way out through the `PaymentClaimable` event. let chanmon_cfgs = create_chanmon_cfgs(2); @@ -200,7 +200,10 @@ mod tests { .unwrap(); let (hash, onion, params) = payment_parameters_from_invoice(&invoice).unwrap(); - nodes[0].node.send_payment(hash, onion, PaymentId(hash.0), params, Retry::Attempts(0)).unwrap(); + nodes[0] + .node + .send_payment(hash, onion, PaymentId(hash.0), params, Retry::Attempts(0)) + .unwrap(); check_added_monitors(&nodes[0], 1); let send_event = SendEvent::from_node(&nodes[0]); nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &send_event.msgs[0]); @@ -214,7 +217,7 @@ mod tests { Event::PaymentClaimable { onion_fields, .. } => { assert_eq!(Some(payment_metadata), onion_fields.unwrap().payment_metadata); }, - _ => panic!("Unexpected event") + _ => panic!("Unexpected event"), } } }