Ok(TaggedField::Route(RouteHint::from_base32(field_data)?)),
constants::TAG_PAYMENT_SECRET =>
Ok(TaggedField::PaymentSecret(PaymentSecret::from_base32(field_data)?)),
+ constants::TAG_FEATURES =>
+ Ok(TaggedField::Features(InvoiceFeatures::from_base32(field_data)?)),
_ => {
// "A reader MUST skip over unknown fields"
Err(ParseError::Skip)
}
#[test]
- fn test_payment_secret_deserialization() {
- use bech32::CheckBase32;
+ fn test_payment_secret_and_features_de_and_ser() {
+ use lightning::ln::features::InvoiceFeatures;
use secp256k1::recovery::{RecoveryId, RecoverableSignature};
use TaggedField::*;
- use {SiPrefix, SignedRawInvoice, Signature, RawInvoice, RawTaggedField, RawHrp, RawDataPart,
+ use {SiPrefix, SignedRawInvoice, Signature, RawInvoice, RawHrp, RawDataPart,
Currency, Sha256, PositiveTimestamp};
- assert_eq!( // BOLT 11 payment secret invoice. The unknown fields are invoice features.
- "lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeessp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q5sqqqqqqqqqqqqqqqpqsq67gye39hfg3zd8rgc80k32tvy9xk2xunwm5lzexnvpx6fd77en8qaq424dxgt56cag2dpt359k3ssyhetktkpqh24jqnjyw6uqd08sgptq44qu".parse(),
- Ok(SignedRawInvoice {
+ // Feature bits 9, 15, and 99 are set.
+ let expected_features = InvoiceFeatures::from_le_bytes(vec![0, 130, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8]);
+ let invoice_str = "lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeessp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q5sqqqqqqqqqqqqqqqpqsq67gye39hfg3zd8rgc80k32tvy9xk2xunwm5lzexnvpx6fd77en8qaq424dxgt56cag2dpt359k3ssyhetktkpqh24jqnjyw6uqd08sgptq44qu";
+ let invoice = SignedRawInvoice {
raw_invoice: RawInvoice {
hrp: RawHrp {
currency: Currency::Bitcoin,
).unwrap())).into(),
Description(::Description::new("coffee beans".to_owned()).unwrap()).into(),
PaymentSecret(::PaymentSecret([17; 32])).into(),
- RawTaggedField::UnknownSemantics(vec![5, 0, 20, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 1, 0, 16,
- 0].check_base32().unwrap())],
- }
+ Features(expected_features).into()]}
},
hash: [0xb1, 0x96, 0x46, 0xc3, 0xbc, 0x56, 0x76, 0x1d, 0x20, 0x65, 0x6e, 0x0e, 0x32,
0xec, 0xd2, 0x69, 0x27, 0xb7, 0x62, 0x6e, 0x2a, 0x8b, 0xe6, 0x97, 0x71, 0x9f,
0x60, 0x82, 0xea, 0xac, 0x81, 0x39, 0x11, 0xda, 0xe0, 0x1a, 0xf3, 0xc1],
RecoveryId::from_i32(1).unwrap()
).unwrap()),
- })
- )
+ };
+ assert_eq!(invoice_str, invoice.to_string());
+ assert_eq!(
+ invoice_str.parse(),
+ Ok(invoice)
+ );
}
#[test]
use bech32::u5;
use bitcoin_hashes::Hash;
use bitcoin_hashes::sha256;
+use lightning::ln::features::InvoiceFeatures;
#[cfg(any(doc, test))]
use lightning::routing::network_graph::RoutingFees;
use lightning::routing::router::RouteHintHop;
Fallback(Fallback),
Route(RouteHint),
PaymentSecret(PaymentSecret),
+ Features(InvoiceFeatures),
}
/// SHA-256 hash
pub const TAG_FALLBACK: u8 = 9;
pub const TAG_ROUTE: u8 = 3;
pub const TAG_PAYMENT_SECRET: u8 = 16;
+ pub const TAG_FEATURES: u8 = 5;
}
impl InvoiceBuilder<tb::False, tb::False, tb::False> {
}
self
}
+
+ /// Adds a features field which indicates the set of supported protocol extensions which the
+ /// origin node supports.
+ pub fn features(mut self, features: InvoiceFeatures) -> Self {
+ self.tagged_fields.push(TaggedField::Features(features));
+ self
+ }
}
impl<D: tb::Bool, H: tb::Bool> InvoiceBuilder<D, H, tb::True> {
find_extract!(self.known_tagged_fields(), TaggedField::PaymentSecret(ref x), x)
}
+ pub fn features(&self) -> Option<&InvoiceFeatures> {
+ find_extract!(self.known_tagged_fields(), TaggedField::Features(ref x), x)
+ }
+
pub fn fallbacks(&self) -> Vec<&Fallback> {
self.known_tagged_fields().filter_map(|tf| match tf {
&TaggedField::Fallback(ref f) => Some(f),
self.signed_invoice.payment_secret()
}
+ /// Get the invoice features if they were included in the invoice
+ pub fn features(&self) -> Option<&InvoiceFeatures> {
+ self.signed_invoice.features()
+ }
+
/// Recover the payee's public key (only to be used if none was included in the invoice)
pub fn recover_payee_pub_key(&self) -> PublicKey {
self.signed_invoice.recover_payee_pub_key().expect("was checked by constructor").0
TaggedField::Fallback(_) => constants::TAG_FALLBACK,
TaggedField::Route(_) => constants::TAG_ROUTE,
TaggedField::PaymentSecret(_) => constants::TAG_PAYMENT_SECRET,
+ TaggedField::Features(_) => constants::TAG_FEATURES,
};
u5::try_from_u8(tag).expect("all tags defined are <32")
TaggedField::PaymentSecret(ref payment_secret) => {
write_tagged_field(writer, constants::TAG_PAYMENT_SECRET, payment_secret)
},
-
+ TaggedField::Features(ref features) => {
+ write_tagged_field(writer, constants::TAG_FEATURES, features)
+ },
}
}
}
use std::{cmp, fmt};
use std::marker::PhantomData;
+use bitcoin::bech32;
+use bitcoin::bech32::{Base32Len, FromBase32, ToBase32, u5, WriteBase32};
use ln::msgs::DecodeError;
use util::ser::{Readable, Writeable, Writer};
required_features: [$( $( $required_feature: ident )|*, )*],
optional_features: [$( $( $optional_feature: ident )|*, )*],
}) => {
+ #[derive(Eq, PartialEq)]
pub struct $context {}
impl Context for $context {
/// appears.
///
/// (C-not exported) as we map the concrete feature types below directly instead
+#[derive(Eq)]
pub struct Features<T: sealed::Context> {
/// Note that, for convenience, flags is LITTLE endian (despite being big-endian on the wire)
flags: Vec<u8>,
}
}
+impl ToBase32 for InvoiceFeatures {
+ fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::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<u5> = 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 InvoiceFeatures {
+ fn base32_len(&self) -> usize {
+ self.to_base32().len()
+ }
+}
+
+impl FromBase32 for InvoiceFeatures {
+ type Err = bech32::Error;
+
+ fn from_base32(field_data: &[u5]) -> Result<InvoiceFeatures, bech32::Error> {
+ // 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<u8> = 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(InvoiceFeatures::from_le_bytes(res_bytes))
+ }
+}
+
impl<T: sealed::Context> Features<T> {
/// Create a blank Features with no features set
pub fn empty() -> Self {
Features::<C> { flags, mark: PhantomData, }
}
- /// Create a Features given a set of flags, in LE.
+ /// Create a Features given a set of flags, in little-endian. This is in reverse byte order from
+ /// most on-the-wire encodings.
pub fn from_le_bytes(flags: Vec<u8>) -> Features<T> {
Features {
flags,
#[cfg(test)]
mod tests {
use super::{ChannelFeatures, InitFeatures, InvoiceFeatures, NodeFeatures};
+ use bitcoin::bech32::{Base32Len, FromBase32, ToBase32, u5};
#[test]
fn sanity_test_known_features() {
assert!(features.requires_payment_secret());
assert!(features.supports_payment_secret());
}
+
+ #[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 = InvoiceFeatures::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 = InvoiceFeatures::from_base32(&features_as_u5s).unwrap();
+ assert_eq!(features, features_deserialized);
+ }
}