From 1fa1e99c1fd60fba67c0a1acad179b9ae81986b3 Mon Sep 17 00:00:00 2001 From: optout <13562139+optout21@users.noreply.github.com> Date: Mon, 26 Aug 2024 16:59:17 +0200 Subject: [PATCH] Invoice serialization in iterative style --- fuzz/src/bolt11_deser.rs | 10 +- lightning-invoice/src/de.rs | 24 +- lightning-invoice/src/lib.rs | 65 ++-- lightning-invoice/src/ser.rs | 462 ++++++++++----------------- lightning-invoice/src/test_ser_de.rs | 19 +- lightning-types/src/features.rs | 5 - 6 files changed, 223 insertions(+), 362 deletions(-) diff --git a/fuzz/src/bolt11_deser.rs b/fuzz/src/bolt11_deser.rs index dc91c0ab49b..92dde80aa66 100644 --- a/fuzz/src/bolt11_deser.rs +++ b/fuzz/src/bolt11_deser.rs @@ -11,8 +11,8 @@ use crate::utils::test_logger; use bech32::Fe32; use bitcoin::secp256k1::{Secp256k1, SecretKey}; use lightning_invoice::{ - Bolt11Invoice, FromBase32, RawBolt11Invoice, RawDataPart, RawHrp, RawTaggedField, TaggedField, - ToBase32, + Base32Iterable, Bolt11Invoice, FromBase32, RawBolt11Invoice, RawDataPart, RawHrp, + RawTaggedField, TaggedField, }; use std::str::FromStr; @@ -32,13 +32,13 @@ pub fn do_test(data: &[u8], _out: Out) { Err(_) => return, }; + let invoice_data_base32 = invoice_data.fe_iter().collect::>(); // Our data encoding is not worse than the input - assert!(invoice_data.to_base32().len() <= bech32.len()); + assert!(invoice_data_base32.len() <= bech32.len()); // Our data serialization is loss-less assert_eq!( - RawDataPart::from_base32(&invoice_data.to_base32()) - .expect("faild parsing out own encoding"), + RawDataPart::from_base32(&invoice_data_base32).expect("faild parsing out own encoding"), invoice_data ); diff --git a/lightning-invoice/src/de.rs b/lightning-invoice/src/de.rs index 9eea151ba63..3330e490d20 100644 --- a/lightning-invoice/src/de.rs +++ b/lightning-invoice/src/de.rs @@ -23,10 +23,19 @@ use bitcoin::secp256k1::PublicKey; use super::{Bolt11Invoice, Sha256, TaggedField, ExpiryTime, MinFinalCltvExpiryDelta, Fallback, PayeePubKey, Bolt11InvoiceSignature, PositiveTimestamp, Bolt11SemanticError, PrivateRoute, Bolt11ParseError, ParseOrSemanticError, Description, RawTaggedField, Currency, RawHrp, SiPrefix, RawBolt11Invoice, - constants, SignedRawBolt11Invoice, RawDataPart, Bolt11InvoiceFeatures, FromBase32}; + constants, SignedRawBolt11Invoice, RawDataPart, Bolt11InvoiceFeatures}; use self::hrp_sm::parse_hrp; +/// Trait for paring/converting base32 slice. +pub trait FromBase32: Sized { + /// The associated error which can be returned from parsing (e.g. because of bad padding). + type Err; + + /// Convert a base32 slice to `Self`. + fn from_base32(b32: &[Fe32]) -> Result; +} + // FromBase32 implementations are here, because the trait is in this module. impl FromBase32 for Vec { @@ -40,22 +49,21 @@ impl FromBase32 for Vec { impl FromBase32 for PaymentSecret { type Err = CheckedHrpstringError; - fn from_base32(field_data: &[Fe32]) -> Result { + fn from_base32(field_data: &[Fe32]) -> Result { if field_data.len() != 52 { return Err(CheckedHrpstringError::Checksum(ChecksumError::InvalidLength)) // TODO(bech32): not entirely accurate - } 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)) } + 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 FromBase32 for Bolt11InvoiceFeatures { type Err = CheckedHrpstringError; - fn from_base32(field_data: &[Fe32]) -> Result { + fn from_base32(field_data: &[Fe32]) -> 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; diff --git a/lightning-invoice/src/lib.rs b/lightning-invoice/src/lib.rs index d4d21348729..ecc5be37e78 100644 --- a/lightning-invoice/src/lib.rs +++ b/lightning-invoice/src/lib.rs @@ -77,51 +77,11 @@ mod prelude { use crate::prelude::*; -/// Interface to write `Fe32`s into a sink -pub trait WriteBase32 { - /// Write error - type Err: fmt::Debug; - - /// Write a `Fe32` slice - fn write(&mut self, data: &Vec) -> Result<(), Self::Err> { - for b in data { - self.write_fe32(*b)?; - } - Ok(()) - } - - /// Write a single `Fe32` - fn write_fe32(&mut self, data: Fe32) -> Result<(), Self::Err>; -} - -/// A trait for converting a value to a type `T` that represents a `Fe32` slice. -pub trait ToBase32 { - /// Convert `Self` to base32 vector - fn to_base32(&self) -> Vec { - let mut vec = Vec::new(); - self.write_base32(&mut vec).unwrap(); - vec - } - - /// Encode as base32 and write it to the supplied writer - /// Implementations shouldn't allocate. - fn write_base32(&self, writer: &mut W) -> Result<(), ::Err>; -} - -/// Interface to calculate the length of the base32 representation before actually serializing -pub trait Base32Len: ToBase32 { - /// Calculate the base32 serialized length - fn base32_len(&self) -> usize; -} - -/// Trait for paring/converting base32 slice. It is the reciprocal of `ToBase32`. -pub trait FromBase32: Sized { - /// The associated error which can be returned from parsing (e.g. because of bad padding). - type Err; - - /// Convert a base32 slice to `Self`. - fn from_base32(b32: &[Fe32]) -> Result; -} +/// Re-export serialization traits +#[cfg(fuzzing)] +pub use crate::ser::Base32Iterable; +#[cfg(fuzzing)] +pub use crate::de::FromBase32; /// Errors that indicate what is wrong with the invoice. They have some granularity for debug /// reasons, but should generally result in an "invalid BOLT11 invoice" message for the user. @@ -347,6 +307,15 @@ pub struct RawHrp { pub si_prefix: Option, } +impl RawHrp { + /// Convert to bech32::Hrp + pub fn to_hrp(&self) -> bech32::Hrp { + let hrp_str = self.to_string(); + let s = core::str::from_utf8(&hrp_str.as_bytes()).expect("asserted to be ASCII"); + bech32::Hrp::parse_unchecked(s) + } +} + /// Data of the [`RawBolt11Invoice`] that is encoded in the data part #[derive(Eq, PartialEq, Debug, Clone, Hash, Ord, PartialOrd)] pub struct RawDataPart { @@ -1024,6 +993,8 @@ 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: &[Fe32]) -> [u8; 32] { + use crate::de::FromBase32; + let mut preimage = Vec::::from(hrp_bytes); let mut data_part = Vec::from(data_without_signature); @@ -1048,9 +1019,11 @@ impl RawBolt11Invoice { /// Calculate the hash of the encoded `RawBolt11Invoice` which should be signed. pub fn signable_hash(&self) -> [u8; 32] { + use crate::ser::Base32Iterable; + RawBolt11Invoice::hash_from_parts( self.hrp.to_string().as_bytes(), - &self.data.to_base32() + &self.data.fe_iter().collect::>(), ) } diff --git a/lightning-invoice/src/ser.rs b/lightning-invoice/src/ser.rs index 1aff8a13d86..d8ed2440c19 100644 --- a/lightning-invoice/src/ser.rs +++ b/lightning-invoice/src/ser.rs @@ -1,39 +1,74 @@ use core::fmt; use core::fmt::{Display, Formatter}; -use bech32::{Bech32, ByteIterExt, Fe32, Fe32IterExt, Hrp}; +use core::{array, iter}; +use alloc::boxed::Box; + +use bech32::{ByteIterExt, Fe32, Fe32IterExt}; use crate::prelude::*; use super::{Bolt11Invoice, Bolt11InvoiceFeatures, Sha256, TaggedField, ExpiryTime, MinFinalCltvExpiryDelta, Fallback, PayeePubKey, Bolt11InvoiceSignature, PaymentSecret, PositiveTimestamp, - PrivateRoute, Description, RawTaggedField, Currency, RawHrp, SiPrefix, constants, SignedRawBolt11Invoice, RawDataPart, WriteBase32, ToBase32, Base32Len}; + PrivateRoute, Description, RawTaggedField, Currency, RawHrp, SiPrefix, constants, SignedRawBolt11Invoice, RawDataPart, RouteHintHop}; + +/// Objects that can be encoded to base32 (bech32). +/// +/// Private to this crate to avoid polluting the API. +pub trait Base32Iterable { + /// apoelstra: In future we want to replace this Box with an explicit + /// associated type, to avoid the allocation. But we cannot do this until + /// Rust 1.65 and GATs since the iterator may contain a reference to self. + fn fe_iter<'s>(&'s self) -> Box + 's>; +} -// ToBase32 & Base32Len implementations are here, because the trait is in this module. +/// Interface to calculate the length of the base32 representation before actually serializing +pub(crate) trait Base32Len: Base32Iterable { + /// Calculate the bech32 serialized length + fn base32_len(&self) -> usize; +} -impl WriteBase32 for Vec { - type Err = (); +// Base32Iterable & Base32Len implementations are here, because the traits are in this module. - fn write_fe32(&mut self, data: Fe32) -> Result<(), Self::Err> { - self.push(data); - Ok(()) +impl Base32Iterable for [u8; N] { + fn fe_iter<'s>(&'s self) -> Box + 's> { + Box::new((*self).into_iter().bytes_to_fes()) + } +} + +impl Base32Len for [u8; N] { + /// Calculate the base32 serialized length + fn base32_len(&self) -> usize { + bytes_size_to_base32_size(N) } } -impl ToBase32 for Vec { - /// Encode as base32 and write it to the supplied writer - fn write_base32(&self, writer: &mut W) -> Result<(), ::Err> { - writer.write(&self.iter().copied().bytes_to_fes().collect::>()) +impl Base32Iterable for [u8] { + fn fe_iter<'s>(&'s self) -> Box + 's> { + Box::new(self.iter().copied().bytes_to_fes()) + } +} + +impl Base32Len for [u8] { + /// Calculate the base32 serialized length + fn base32_len(&self) -> usize { + bytes_size_to_base32_size(self.len()) + } +} + +impl Base32Iterable for Vec { + fn fe_iter<'s>(&'s self) -> Box + 's> { + Box::new(self.iter().copied().bytes_to_fes()) } } impl Base32Len for Vec { /// Calculate the base32 serialized length fn base32_len(&self) -> usize { - (self.len() * 8 + 4) / 5 + bytes_size_to_base32_size(self.len()) } } -impl ToBase32 for PaymentSecret { - fn write_base32(&self, writer: &mut W) -> Result<(), ::Err> { - (&self.0[..]).to_vec().write_base32(writer) +impl Base32Iterable for PaymentSecret { + fn fe_iter<'s>(&'s self) -> Box + 's> { + Box::new(self.0[..].fe_iter()) } } @@ -43,8 +78,10 @@ impl Base32Len for PaymentSecret { } } -impl ToBase32 for Bolt11InvoiceFeatures { - fn write_base32(&self, writer: &mut W) -> Result<(), ::Err> { +impl Base32Iterable for Bolt11InvoiceFeatures { + fn fe_iter<'s>(&'s self) -> Box + 's> { + // Note: here bytes are grouped into 5-bit values from rigth-to-left, therefore + // Bech32 convversion cannot be used. // 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.le_flags().len() * 8 + 4) / 5 as usize; @@ -69,101 +106,15 @@ impl ToBase32 for Bolt11InvoiceFeatures { while !res_u5s.is_empty() && res_u5s[0] == Fe32::Q { res_u5s.remove(0); } - writer.write(&res_u5s) + + Box::new(res_u5s.into_iter()) } } impl Base32Len for Bolt11InvoiceFeatures { fn base32_len(&self) -> usize { - self.to_base32().len() - } -} - -/// Converts a stream of bytes written to it to base32. On finalization the according padding will -/// be applied. That means the results of writing two data blocks with one or two `BytesToBase32` -/// converters will differ. -struct BytesToBase32<'a, W: WriteBase32 + 'a> { - /// Target for writing the resulting `Fe32`s resulting from the written bytes - writer: &'a mut W, - /// Holds all unwritten bits left over from last round. The bits are stored beginning from - /// the most significant bit. E.g. if buffer_bits=3, then the byte with bits a, b and c will - /// look as follows: [a, b, c, 0, 0, 0, 0, 0] - buffer: u8, - /// Amount of bits left over from last round, stored in buffer. - buffer_bits: u8, -} - -impl<'a, W: WriteBase32> BytesToBase32<'a, W> { - /// Create a new bytes-to-base32 converter with `writer` as a sink for the resulting base32 - /// data. - pub fn new(writer: &'a mut W) -> BytesToBase32<'a, W> { - BytesToBase32 { - writer, - buffer: 0, - buffer_bits: 0, - } - } - - /// Add more bytes to the current conversion unit - pub fn append(&mut self, bytes: &[u8]) -> Result<(), W::Err> { - for b in bytes { - self.append_u8(*b)?; - } - Ok(()) - } - - pub fn append_u8(&mut self, byte: u8) -> Result<(), W::Err> { - // Write first Fe32 if we have to write two Fe32s this round. That only happens if the - // buffer holds too many bits, so we don't have to combine buffer bits with new bits - // from this rounds byte. - if self.buffer_bits >= 5 { - self.writer.write_fe32( - Fe32::try_from((self.buffer & 0b11111000) >> 3 ).expect("<32") - )?; - self.buffer <<= 5; - self.buffer_bits -= 5; - } - - // Combine all bits from buffer with enough bits from this rounds byte so that they fill - // a Fe32. Save remaining bits from byte to buffer. - let from_buffer = self.buffer >> 3; - let from_byte = byte >> (3 + self.buffer_bits); // buffer_bits <= 4 - - self.writer.write_fe32(Fe32::try_from(from_buffer | from_byte).expect("<32"))?; - self.buffer = byte << (5 - self.buffer_bits); - self.buffer_bits += 3; - - Ok(()) - } - - pub fn finalize(mut self) -> Result<(), W::Err> { - self.inner_finalize()?; - core::mem::forget(self); - Ok(()) - } - - fn inner_finalize(&mut self) -> Result<(), W::Err>{ - // There can be at most two Fe32s left in the buffer after processing all bytes, write them. - if self.buffer_bits >= 5 { - self.writer.write_fe32( - Fe32::try_from((self.buffer & 0b11111000) >> 3).expect("<32") - )?; - self.buffer <<= 5; - self.buffer_bits -= 5; - } - - if self.buffer_bits != 0 { - self.writer.write_fe32(Fe32::try_from(self.buffer >> 3).expect("<32"))?; - } - - Ok(()) - } -} - -impl<'a, W: WriteBase32> Drop for BytesToBase32<'a, W> { - fn drop(&mut self) { - self.inner_finalize() - .expect("Unhandled error when finalizing conversion on drop. User finalize to handle.") + // Here we actually do the conversion + self.fe_iter().collect::>().len() } } @@ -180,27 +131,28 @@ fn bytes_size_to_base32_size(byte_size: usize) -> usize { } impl Display for Bolt11Invoice { - fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { self.signed_invoice.fmt(f) } } impl Display for SignedRawBolt11Invoice { - fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { - let hrp = self.raw_invoice.hrp.to_string(); - let mut data = self.raw_invoice.data.to_base32(); - data.extend_from_slice(&self.signature.to_base32()); - - let bech32 = data.iter().copied().with_checksum::(&Hrp::parse(&hrp).expect("not a valid hrp string")).chars().collect::(); - f.write_str(&bech32)?; - + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let hrp = self.raw_invoice.hrp.to_hrp(); + for ch in + self.raw_invoice.data.fe_iter() + .chain(self.signature.fe_iter()) + .with_checksum::(&hrp).chars() + { + write!(f, "{}", ch)?; + } Ok(()) } } /// This is not exported to bindings users impl Display for RawHrp { - fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { let amount = match self.raw_amount { Some(ref amt) => amt.to_string(), None => String::new(), @@ -222,7 +174,7 @@ impl Display for RawHrp { } impl Display for Currency { - fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { let currency_code = match *self { Currency::Bitcoin => "bc", Currency::BitcoinTestnet => "tb", @@ -235,7 +187,7 @@ impl Display for Currency { } impl Display for SiPrefix { - fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "{}", match *self { SiPrefix::Milli => "m", @@ -247,11 +199,13 @@ impl Display for SiPrefix { } } +/// Encode an integer to base32, big endian, without leading zeros fn encode_int_be_base32(int: u64) -> Vec { let base = 32u64; - let mut out_vec = Vec::::new(); - + const LEN: usize = (64 + 4) / 5; + debug_assert!(LEN == 13); // for validating LEN (mutants) + let mut out_vec = Vec::::with_capacity(LEN); let mut rem_int = int; while rem_int != 0 { out_vec.push(Fe32::try_from((rem_int % base) as u8).expect("always <32")); @@ -262,112 +216,69 @@ fn encode_int_be_base32(int: u64) -> Vec { out_vec } +/// The length of the output of `encode_int_be_base32`. fn encoded_int_be_base32_size(int: u64) -> usize { - for pos in (0..13).rev() { - if int & (0x1f << (5 * pos)) != 0 { - return (pos + 1) as usize; - } - } - 0usize -} - -fn encode_int_be_base256>(int: T) -> Vec { - let base = 256u64; - - let mut out_vec = Vec::::new(); - - let mut rem_int: u64 = int.into(); - while rem_int != 0 { - out_vec.push((rem_int % base) as u8); - rem_int /= base; - } - - out_vec.reverse(); - out_vec + let bit_len = 64 - int.leading_zeros() as usize; // cast ok as value is in 0..=64. + (bit_len + 4) / 5 } -/// Appends the default value of `T` to the front of the `in_vec` till it reaches the length -/// `target_length`. If `in_vec` already is too lang `None` is returned. -// TODO(bech32): Default value parameter is added because `bech32::Fe32` does not have `Default`, but it will after 0.12. -// If it get's added, the `default_value` parameter should be dropped (https://github.com/rust-bitcoin/rust-bech32/pull/184) -fn try_stretch(mut in_vec: Vec, target_len: usize, default_value: T) -> Option> - where T: Copy -{ - if in_vec.len() > target_len { - None - } else if in_vec.len() == target_len { - Some(in_vec) - } else { - let mut out_vec = Vec::::with_capacity(target_len); - out_vec.append(&mut vec![default_value; target_len - in_vec.len()]); - out_vec.append(&mut in_vec); - Some(out_vec) - } -} - -impl ToBase32 for RawDataPart { - fn write_base32(&self, writer: &mut W) -> Result<(), ::Err> { - // encode timestamp - self.timestamp.write_base32(writer)?; - - // encode tagged fields - for tagged_field in self.tagged_fields.iter() { - tagged_field.write_base32(writer)?; - } - - Ok(()) +impl Base32Iterable for RawDataPart { + fn fe_iter<'s>(&'s self) -> Box + 's> { + let ts_iter = self.timestamp.fe_iter(); + let fields_iter = self.tagged_fields.iter().map(RawTaggedField::fe_iter).flatten(); + Box::new(ts_iter.chain(fields_iter)) } } -impl ToBase32 for PositiveTimestamp { - fn write_base32(&self, writer: &mut W) -> Result<(), ::Err> { - // FIXME: use writer for int encoding - writer.write( - &try_stretch(encode_int_be_base32(self.as_unix_timestamp()), 7, Fe32::Q) - .expect("Can't be longer due than 7 u5s due to timestamp bounds") - ) +impl Base32Iterable for PositiveTimestamp { + fn fe_iter<'s>(&'s self) -> Box + 's> { + let fes = encode_int_be_base32(self.as_unix_timestamp()); + debug_assert!(fes.len() <= 7, "Invalid timestamp length"); + let to_pad = 7 - fes.len(); + Box::new(core::iter::repeat(Fe32::Q).take(to_pad).chain(fes.into_iter())) } } -impl ToBase32 for RawTaggedField { - fn write_base32(&self, writer: &mut W) -> Result<(), ::Err> { +impl Base32Iterable for RawTaggedField { + fn fe_iter<'s>(&'s self) -> Box + 's> { + // Annoyingly, when we move to explicit types, we will need an + // explicit enum holding the two iterator variants. match *self { RawTaggedField::UnknownSemantics(ref content) => { - writer.write(content) + Box::new(content.iter().copied()) }, - RawTaggedField::KnownSemantics(ref tagged_field) => { - tagged_field.write_base32(writer) - } + RawTaggedField::KnownSemantics(ref tagged_field) => tagged_field.fe_iter(), } } } -impl ToBase32 for Sha256 { - fn write_base32(&self, writer: &mut W) -> Result<(), ::Err> { - (&self.0[..].to_vec()).write_base32(writer) +impl Base32Iterable for Sha256 { + fn fe_iter<'s>(&'s self) -> Box + 's> { + Box::new(self.0[..].fe_iter()) } } + impl Base32Len for Sha256 { fn base32_len(&self) -> usize { - (&self.0[..].to_vec()).base32_len() + self.0[..].base32_len() } } -impl ToBase32 for Description { - fn write_base32(&self, writer: &mut W) -> Result<(), ::Err> { - self.0.0.as_bytes().to_vec().write_base32(writer) +impl Base32Iterable for Description { + fn fe_iter<'s>(&'s self) -> Box + 's> { + Box::new(self.0.0.as_bytes().fe_iter()) } } impl Base32Len for Description { fn base32_len(&self) -> usize { - self.0.0.as_bytes().to_vec().base32_len() + self.0.0.as_bytes().base32_len() } } -impl ToBase32 for PayeePubKey { - fn write_base32(&self, writer: &mut W) -> Result<(), ::Err> { - (&self.serialize()[..].to_vec()).write_base32(writer) +impl Base32Iterable for PayeePubKey { + fn fe_iter<'s>(&'s self) -> Box + 's> { + Box::new(self.serialize().into_iter().bytes_to_fes()) } } @@ -377,9 +288,9 @@ impl Base32Len for PayeePubKey { } } -impl ToBase32 for ExpiryTime { - fn write_base32(&self, writer: &mut W) -> Result<(), ::Err> { - writer.write(&encode_int_be_base32(self.as_seconds())) +impl Base32Iterable for ExpiryTime { + fn fe_iter<'s>(&'s self) -> Box + 's> { + Box::new(encode_int_be_base32(self.as_seconds()).into_iter()) } } @@ -389,9 +300,9 @@ impl Base32Len for ExpiryTime { } } -impl ToBase32 for MinFinalCltvExpiryDelta { - fn write_base32(&self, writer: &mut W) -> Result<(), ::Err> { - writer.write(&encode_int_be_base32(self.0)) +impl Base32Iterable for MinFinalCltvExpiryDelta { + fn fe_iter<'s>(&'s self) -> Box + 's> { + Box::new(encode_int_be_base32(self.0).into_iter()) } } @@ -401,22 +312,22 @@ impl Base32Len for MinFinalCltvExpiryDelta { } } -impl ToBase32 for Fallback { - fn write_base32(&self, writer: &mut W) -> Result<(), ::Err> { - match *self { +impl Base32Iterable for Fallback { + fn fe_iter<'s>(&'s self) -> Box + 's> { + Box::new(match *self { Fallback::SegWitProgram {version: v, program: ref p} => { - writer.write_fe32(Fe32::try_from(v.to_num()).expect("witness version <= 16"))?; - p.write_base32(writer) - }, + let v = Fe32::try_from(v.to_num()).expect("valid version"); + core::iter::once(v).chain(p[..].fe_iter()) + } Fallback::PubKeyHash(ref hash) => { - writer.write_fe32(Fe32::try_from(17).expect("17 < 32"))?; - (&hash[..].to_vec()).write_base32(writer) - }, + // 17 '3' + core::iter::once(Fe32::_3).chain(hash[..].fe_iter()) + } Fallback::ScriptHash(ref hash) => { - writer.write_fe32(Fe32::try_from(18).expect("18 < 32"))?; - (&hash[..].to_vec()).write_base32(writer) + // 18 'J' + core::iter::once(Fe32::J).chain(hash[..].fe_iter()) } - } + }) } } @@ -433,39 +344,21 @@ impl Base32Len for Fallback { } } -impl ToBase32 for PrivateRoute { - fn write_base32(&self, writer: &mut W) -> Result<(), ::Err> { - let mut converter = BytesToBase32::new(writer); - - for hop in (self.0).0.iter() { - converter.append(&hop.src_node_id.serialize()[..])?; - let short_channel_id = try_stretch( - encode_int_be_base256(hop.short_channel_id), - 8, u8::default() - ).expect("sizeof(u64) == 8"); - converter.append(&short_channel_id)?; - - let fee_base_msat = try_stretch( - encode_int_be_base256(hop.fees.base_msat), - 4, u8::default() - ).expect("sizeof(u32) == 4"); - converter.append(&fee_base_msat)?; - - let fee_proportional_millionths = try_stretch( - encode_int_be_base256(hop.fees.proportional_millionths), - 4, u8::default() - ).expect("sizeof(u32) == 4"); - converter.append(&fee_proportional_millionths)?; - - let cltv_expiry_delta = try_stretch( - encode_int_be_base256(hop.cltv_expiry_delta), - 2, u8::default() - ).expect("sizeof(u16) == 2"); - converter.append(&cltv_expiry_delta)?; +// Shorthand type +type RouteHintHopIter = iter::Chain, array::IntoIter>, array::IntoIter>, array::IntoIter>, array::IntoIter>; + +impl Base32Iterable for PrivateRoute { + fn fe_iter<'s>(&'s self) -> Box + 's> { + fn serialize_to_iter(hop: &RouteHintHop) -> RouteHintHopIter { + let i1 = hop.src_node_id.serialize().into_iter(); + let i2 = u64::to_be_bytes(hop.short_channel_id).into_iter(); + let i3 = u32::to_be_bytes(hop.fees.base_msat).into_iter(); + let i4 = u32::to_be_bytes(hop.fees.proportional_millionths).into_iter(); + let i5 = u16::to_be_bytes(hop.cltv_expiry_delta).into_iter(); + i1.chain(i2).chain(i3).chain(i4).chain(i5) } - converter.finalize()?; - Ok(()) + Box::new(self.0.0.iter().map(serialize_to_iter).flatten().bytes_to_fes()) } } @@ -475,70 +368,73 @@ impl Base32Len for PrivateRoute { } } -impl ToBase32 for TaggedField { - fn write_base32(&self, writer: &mut W) -> Result<(), ::Err> { +// Shorthand type +type TaggedFieldIter = core::iter::Chain, I>; + +impl Base32Iterable for TaggedField { + fn fe_iter<'s>(&'s self) -> Box + 's> { /// Writes a tagged field: tag, length and data. `tag` should be in `0..32` otherwise the /// function will panic. - fn write_tagged_field(writer: &mut W, tag: u8, payload: &P) -> Result<(), W::Err> - where W: WriteBase32, - P: ToBase32 + Base32Len, + fn write_tagged_field<'s, P>(tag: u8, payload: &'s P) -> TaggedFieldIter + 's>> + where P: Base32Iterable + Base32Len + ?Sized { let len = payload.base32_len(); assert!(len < 1024, "Every tagged field data can be at most 1023 bytes long."); - writer.write_fe32(Fe32::try_from(tag).expect("invalid tag, not in 0..32"))?; - writer.write(&try_stretch( - encode_int_be_base32(len as u64), - 2, Fe32::Q - ).expect("Can't be longer than 2, see assert above."))?; - payload.write_base32(writer) + [ + Fe32::try_from(tag).expect("invalid tag, not in 0..32"), + Fe32::try_from((len / 32) as u8).expect("< 32"), + Fe32::try_from((len % 32) as u8).expect("< 32"), + ].into_iter().chain(payload.fe_iter()) } - match *self { + // we will also need a giant enum for this + Box::new(match *self { TaggedField::PaymentHash(ref hash) => { - write_tagged_field(writer, constants::TAG_PAYMENT_HASH, hash) + write_tagged_field(constants::TAG_PAYMENT_HASH, hash) }, TaggedField::Description(ref description) => { - write_tagged_field(writer, constants::TAG_DESCRIPTION, description) + write_tagged_field(constants::TAG_DESCRIPTION, description) }, TaggedField::PayeePubKey(ref pub_key) => { - write_tagged_field(writer, constants::TAG_PAYEE_PUB_KEY, pub_key) + write_tagged_field(constants::TAG_PAYEE_PUB_KEY, pub_key) }, TaggedField::DescriptionHash(ref hash) => { - write_tagged_field(writer, constants::TAG_DESCRIPTION_HASH, hash) + write_tagged_field(constants::TAG_DESCRIPTION_HASH, hash) }, TaggedField::ExpiryTime(ref duration) => { - write_tagged_field(writer, constants::TAG_EXPIRY_TIME, duration) + write_tagged_field(constants::TAG_EXPIRY_TIME, duration) }, TaggedField::MinFinalCltvExpiryDelta(ref expiry) => { - write_tagged_field(writer, constants::TAG_MIN_FINAL_CLTV_EXPIRY_DELTA, expiry) + write_tagged_field(constants::TAG_MIN_FINAL_CLTV_EXPIRY_DELTA, expiry) }, TaggedField::Fallback(ref fallback_address) => { - write_tagged_field(writer, constants::TAG_FALLBACK, fallback_address) + write_tagged_field(constants::TAG_FALLBACK, fallback_address) }, TaggedField::PrivateRoute(ref route_hops) => { - write_tagged_field(writer, constants::TAG_PRIVATE_ROUTE, route_hops) + write_tagged_field(constants::TAG_PRIVATE_ROUTE, route_hops) }, TaggedField::PaymentSecret(ref payment_secret) => { - write_tagged_field(writer, constants::TAG_PAYMENT_SECRET, payment_secret) + write_tagged_field(constants::TAG_PAYMENT_SECRET, payment_secret) }, TaggedField::PaymentMetadata(ref payment_metadata) => { - write_tagged_field(writer, constants::TAG_PAYMENT_METADATA, payment_metadata) + write_tagged_field(constants::TAG_PAYMENT_METADATA, payment_metadata) }, TaggedField::Features(ref features) => { - write_tagged_field(writer, constants::TAG_FEATURES, features) + write_tagged_field(constants::TAG_FEATURES, features) }, - } + }) } } -impl ToBase32 for Bolt11InvoiceSignature { - fn write_base32(&self, writer: &mut W) -> Result<(), ::Err> { - let mut converter = BytesToBase32::new(writer); +impl Base32Iterable for Bolt11InvoiceSignature { + fn fe_iter<'s>(&'s self) -> Box + 's> { let (recovery_id, signature) = self.0.serialize_compact(); - converter.append(&signature[..])?; - converter.append_u8(recovery_id.to_i32() as u8)?; - converter.finalize() + Box::new( + signature.into_iter() + .chain(core::iter::once(recovery_id.to_i32() as u8)) + .bytes_to_fes() + ) } } @@ -574,18 +470,8 @@ mod test { use bech32::Fe32; let input: u64 = 33764; - let expected_out = &[1, 0, 31, 4].iter().copied().map(|v| Fe32::try_from(v).expect("should be <= 31")).collect::>(); - - assert_eq!(expected_out.iter().copied().map(|v| v.to_char()).collect::(), encode_int_be_base32(input).iter().copied().map(|v| v.to_char()).collect::()); - } - - #[test] - fn test_encode_int_be_base256() { - use crate::ser::encode_int_be_base256; - - let input: u64 = 16842530; - let expected_out = vec![1, 0, 255, 34]; + let expected_out = [1, 0, 31, 4].iter().copied().map(|v| Fe32::try_from(v).expect("<= 31")).collect::>(); - assert_eq!(expected_out, encode_int_be_base256(input)); + assert_eq!(expected_out, encode_int_be_base32(input)); } } diff --git a/lightning-invoice/src/test_ser_de.rs b/lightning-invoice/src/test_ser_de.rs index 2025d4ae170..dac9b1495f9 100644 --- a/lightning-invoice/src/test_ser_de.rs +++ b/lightning-invoice/src/test_ser_de.rs @@ -1,20 +1,19 @@ -use crate::{ - sha256, FromBase32, PayeePubKey, PaymentSecret, PositiveTimestamp, RawDataPart, Sha256, -}; -use bech32::{Base32Len, ToBase32}; - +use crate::de::FromBase32; +use crate::ser::{Base32Iterable, Base32Len}; +use crate::{sha256, PayeePubKey, PaymentSecret, PositiveTimestamp, RawDataPart, Sha256}; +use bech32::Fe32; use core::fmt::Debug; use std::str::FromStr; /// Test base32 encode and decode fn ser_de_test(o: T, expected_str: &str) where - T: ToBase32 + FromBase32 + Eq + Debug, + T: Base32Iterable + FromBase32 + Eq + Debug, T::Err: Debug, { - let serialized_32 = o.to_base32(); + let serialized_32 = o.fe_iter().collect::>(); let serialized_str = serialized_32.iter().map(|f| f.to_char()).collect::(); - assert_eq!(serialized_str, expected_str); + assert_eq!(serialized_str, expected_str, "Mismatch for {:?}", o); // deserialize back let o2 = T::from_base32(&serialized_32).unwrap(); @@ -24,10 +23,10 @@ where /// Test base32 encode and decode, and also length hint fn ser_de_test_len(o: T, expected_str: &str) where - T: ToBase32 + FromBase32 + Base32Len + Eq + Debug, + T: Base32Iterable + Base32Len + FromBase32 + Eq + Debug, T::Err: Debug, { - assert_eq!(o.base32_len(), expected_str.len()); + assert_eq!(o.base32_len(), expected_str.len(), "Mismatch for {} {:?}", expected_str, o); ser_de_test(o, expected_str) } diff --git a/lightning-types/src/features.rs b/lightning-types/src/features.rs index d1761c8cf1f..c2b3c7ec1f4 100644 --- a/lightning-types/src/features.rs +++ b/lightning-types/src/features.rs @@ -713,11 +713,6 @@ impl Bolt11InvoiceFeatures { self.to_context_internal() } - /// Accessor for flags - pub fn flags(&self) -> &Vec { - &self.flags - } - /// 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