//! [BOLT-3](https://github.com/lightning/bolts/blob/master/03-transactions.md) 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
//! [messages]: crate::ln::msgs
use crate::{io, io_extras};
use crate::prelude::*;
use core::{cmp, fmt};
+use core::borrow::Borrow;
use core::hash::{Hash, Hasher};
use core::marker::PhantomData;
// Byte 1
VariableLengthOnion | StaticRemoteKey | PaymentSecret,
// Byte 2
- BasicMPP | Wumbo | AnchorsZeroFeeHtlcTx,
+ BasicMPP | Wumbo | AnchorsNonzeroFeeHtlcTx | AnchorsZeroFeeHtlcTx,
// Byte 3
ShutdownAnySegwit,
// Byte 4
// Byte 1
VariableLengthOnion | StaticRemoteKey | PaymentSecret,
// Byte 2
- BasicMPP | Wumbo | AnchorsZeroFeeHtlcTx,
+ BasicMPP | Wumbo | AnchorsNonzeroFeeHtlcTx | AnchorsZeroFeeHtlcTx,
// Byte 3
ShutdownAnySegwit,
// Byte 4
// Byte 1
StaticRemoteKey,
// Byte 2
- AnchorsZeroFeeHtlcTx,
+ AnchorsNonzeroFeeHtlcTx | AnchorsZeroFeeHtlcTx,
// Byte 3
,
// Byte 4
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);
mark: PhantomData<T>,
}
-impl <T: sealed::Context> Features<T> {
- pub(crate) fn or(mut self, o: Self) -> Self {
- let total_feature_len = cmp::max(self.flags.len(), o.flags.len());
+impl<T: sealed::Context, Rhs: Borrow<Self>> core::ops::BitOrAssign<Rhs> for Features<T> {
+ 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, o_byte) in self.flags.iter_mut().zip(o.flags.iter()) {
- *byte |= *o_byte;
+ for (byte, rhs_byte) in self.flags.iter_mut().zip(rhs.borrow().flags.iter()) {
+ *byte |= *rhs_byte;
}
+ }
+}
+
+impl<T: sealed::Context> core::ops::BitOr for Features<T> {
+ type Output = Self;
+
+ fn bitor(mut self, o: Self) -> Self {
+ self |= o;
self
}
}
self.flags.eq(&o.flags)
}
}
+impl<T: sealed::Context> PartialOrd for Features<T> {
+ fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
+ self.flags.partial_cmp(&other.flags)
+ }
+}
+impl<T: sealed::Context + Eq> Ord for Features<T> {
+ fn cmp(&self, other: &Self) -> cmp::Ordering {
+ self.flags.cmp(&other.flags)
+ }
+}
impl<T: sealed::Context> fmt::Debug for Features<T> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
self.flags.fmt(fmt)
/// [`PaymentParameters::for_keysend`], thus omitting the need for payers to manually construct an
/// `InvoiceFeatures` 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() -> InvoiceFeatures {
+ pub(crate) fn for_keysend(allow_mpp: bool) -> InvoiceFeatures {
let mut res = InvoiceFeatures::empty();
res.set_variable_length_onion_optional();
+ if allow_mpp {
+ res.set_basic_mpp_optional();
+ }
res
}
}
<sealed::ChannelTypeContext as sealed::StaticRemoteKey>::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();
+ <sealed::ChannelTypeContext as sealed::StaticRemoteKey>::set_required_bit(&mut ret.flags);
+ <sealed::ChannelTypeContext as sealed::AnchorsZeroFeeHtlcTx>::set_required_bit(&mut ret.flags);
+ ret
+ }
}
impl ToBase32 for InvoiceFeatures {
self.flags.iter().any(|&byte| (byte & 0b10_10_10_10) != 0)
}
+ /// Returns true if this `Features` object contains required features unknown by `other`.
+ pub fn requires_unknown_bits_from(&self, other: &Features<T>) -> 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)| {
+ const REQUIRED_FEATURES: u8 = 0b01_01_01_01;
+ const OPTIONAL_FEATURES: u8 = 0b10_10_10_10;
+ let unknown_features = if i < other.flags.len() {
+ // Form a mask similar to !T::KNOWN_FEATURE_MASK only for `other`
+ !(other.flags[i]
+ | ((other.flags[i] >> 1) & REQUIRED_FEATURES)
+ | ((other.flags[i] << 1) & OPTIONAL_FEATURES))
+ } else {
+ 0b11_11_11_11
+ };
+ (byte & (REQUIRED_FEATURES & unknown_features)) != 0
+ })
+ }
+
/// Returns true if this `Features` object contains unknown feature flags which are set as
/// "required".
pub fn requires_unknown_bits(&self) -> bool {
}
true
}
+
+ /// 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(());
+ }
+
+ let byte_offset = bit / 8;
+ let mask = 1 << (bit - 8 * byte_offset);
+ if byte_offset < T::KNOWN_FEATURE_MASK.len() {
+ 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<T: sealed::UpfrontShutdownScript> Features<T> {
assert!(features.supports_unknown_bits());
}
+ #[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();
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_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();
- init_features.set_anchors_zero_fee_htlc_tx_optional();
assert!(init_features.initial_routing_sync());
assert!(!init_features.supports_upfront_shutdown_script());
// 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
+ // - basic_mpp | wumbo | anchors_zero_fee_htlc_tx
// - opt_shutdown_anysegwit
// - onion_messages
// - option_channel_type | option_scid_alias
assert!(features.supports_payment_secret());
}
+ #[test]
+ fn set_custom_bits() {
+ let mut features = InvoiceFeatures::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 = <sealed::InvoiceContext as sealed::PaymentSecret>::EVEN_BIT;
+ let byte_offset = <sealed::InvoiceContext as sealed::PaymentSecret>::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 = InvoiceFeatures::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 = InvoiceFeatures::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]);