Skip to content

Commit

Permalink
Invoice serialization in iterative style
Browse files Browse the repository at this point in the history
  • Loading branch information
optout21 committed Aug 30, 2024
1 parent 29c389c commit 22a53d4
Show file tree
Hide file tree
Showing 6 changed files with 223 additions and 362 deletions.
10 changes: 5 additions & 5 deletions fuzz/src/bolt11_deser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -32,13 +32,13 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], _out: Out) {
Err(_) => return,
};

let invoice_data_base32 = invoice_data.fe_iter().collect::<Vec<_>>();
// 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
);

Expand Down
24 changes: 16 additions & 8 deletions lightning-invoice/src/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Self, Self::Err>;
}

// FromBase32 implementations are here, because the trait is in this module.

impl FromBase32 for Vec<u8> {
Expand All @@ -40,22 +49,21 @@ impl FromBase32 for Vec<u8> {
impl FromBase32 for PaymentSecret {
type Err = CheckedHrpstringError;

fn from_base32(field_data: &[Fe32]) -> Result<PaymentSecret, CheckedHrpstringError> {
fn from_base32(field_data: &[Fe32]) -> Result<Self, Self::Err> {
if field_data.len() != 52 {
return Err(CheckedHrpstringError::Checksum(ChecksumError::InvalidLength)) // TODO(bech32): not entirely accurate
} else {
let data_bytes = Vec::<u8>::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::<u8>::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<Bolt11InvoiceFeatures, CheckedHrpstringError> {
fn from_base32(field_data: &[Fe32]) -> Result<Self, Self::Err> {
// 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;
Expand Down
65 changes: 19 additions & 46 deletions lightning-invoice/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Fe32>) -> 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<Fe32> {
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<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::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<Self, Self::Err>;
}
/// 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.
Expand Down Expand Up @@ -347,6 +307,15 @@ pub struct RawHrp {
pub si_prefix: Option<SiPrefix>,
}

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 {
Expand Down Expand Up @@ -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::<u8>::from(hrp_bytes);

let mut data_part = Vec::from(data_without_signature);
Expand All @@ -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::<Vec<Fe32>>(),
)
}

Expand Down
Loading

0 comments on commit 22a53d4

Please sign in to comment.