Skip to content

Commit 5fa337a

Browse files
committed
Rework Bolt11Features serialization, cleaner, better testable
1 parent 22a53d4 commit 5fa337a

File tree

5 files changed

+263
-100
lines changed

5 files changed

+263
-100
lines changed

lightning-invoice/src/de.rs

Lines changed: 80 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ use core::num::ParseIntError;
88
use core::str;
99
use core::str::FromStr;
1010

11+
use bech32::primitives::decode::{CheckedHrpstring, CheckedHrpstringError};
1112
use bech32::{Bech32, Fe32, Fe32IterExt};
12-
use bech32::primitives::decode::{CheckedHrpstring, CheckedHrpstringError, ChecksumError};
1313

1414
use bitcoin::{PubkeyHash, ScriptHash, WitnessVersion};
1515
use bitcoin::hashes::Hash;
@@ -39,19 +39,37 @@ pub trait FromBase32: Sized {
3939
// FromBase32 implementations are here, because the trait is in this module.
4040

4141
impl FromBase32 for Vec<u8> {
42-
type Err = CheckedHrpstringError;
42+
type Err = Bolt11ParseError;
4343

4444
fn from_base32(data: &[Fe32]) -> Result<Self, Self::Err> {
4545
Ok(data.iter().copied().fes_to_bytes().collect::<Self>())
4646
}
4747
}
4848

49+
impl<const N: usize> FromBase32 for [u8; N] {
50+
type Err = Bolt11ParseError;
51+
52+
fn from_base32(data: &[Fe32]) -> Result<Self, Self::Err> {
53+
data.iter().copied().fes_to_bytes().collect::<Vec<_>>().try_into().map_err(|_| {
54+
Bolt11ParseError::InvalidSliceLength(
55+
data.len(),
56+
(N * 8 + 4) / 5,
57+
"<[u8; N]>::from_base32()".into(),
58+
)
59+
})
60+
}
61+
}
62+
4963
impl FromBase32 for PaymentSecret {
50-
type Err = CheckedHrpstringError;
64+
type Err = Bolt11ParseError;
5165

5266
fn from_base32(field_data: &[Fe32]) -> Result<Self, Self::Err> {
5367
if field_data.len() != 52 {
54-
return Err(CheckedHrpstringError::Checksum(ChecksumError::InvalidLength)) // TODO(bech32): not entirely accurate
68+
return Err(Bolt11ParseError::InvalidSliceLength(
69+
field_data.len(),
70+
52,
71+
"PaymentSecret::from_base32()".into(),
72+
));
5573
}
5674
let data_bytes = Vec::<u8>::from_base32(field_data)?;
5775
let mut payment_secret = [0; 32];
@@ -61,28 +79,45 @@ impl FromBase32 for PaymentSecret {
6179
}
6280

6381
impl FromBase32 for Bolt11InvoiceFeatures {
64-
type Err = CheckedHrpstringError;
82+
type Err = Bolt11ParseError;
6583

84+
/// Convert to byte values, by packing the 5-bit groups,
85+
/// putting the 5-bit values from left to-right (reverse order),
86+
/// starting from the rightmost bit,
87+
/// and taking the resulting 8-bit values (right to left),
88+
/// with the leading 0's skipped.
6689
fn from_base32(field_data: &[Fe32]) -> Result<Self, Self::Err> {
67-
// Explanation for the "7": the normal way to round up when dividing is to add the divisor
68-
// minus one before dividing
69-
let length_bytes = (field_data.len() * 5 + 7) / 8 as usize;
70-
let mut res_bytes: Vec<u8> = vec![0; length_bytes];
71-
for (u5_idx, chunk) in field_data.iter().enumerate() {
72-
let bit_pos_from_right_0_indexed = (field_data.len() - u5_idx - 1) * 5;
73-
let new_byte_idx = (bit_pos_from_right_0_indexed / 8) as usize;
74-
let new_bit_pos = bit_pos_from_right_0_indexed % 8;
75-
let chunk_u16 = chunk.to_u8() as u16;
76-
res_bytes[new_byte_idx] |= ((chunk_u16 << new_bit_pos) & 0xff) as u8;
77-
if new_byte_idx != length_bytes - 1 {
78-
res_bytes[new_byte_idx + 1] |= ((chunk_u16 >> (8-new_bit_pos)) & 0xff) as u8;
90+
// Fe32 conversion cannot be used, because this unpacks from right, right-to-left
91+
// Carry bits, 0, 1, 2, 3, or 4 bits
92+
let mut carry_bits = 0;
93+
let mut carry = 0u8;
94+
let mut output = Vec::<u8>::new();
95+
96+
// Iterate over input in reverse
97+
for curr_in in field_data.iter().rev() {
98+
let curr_in_as_u8 = curr_in.to_u8();
99+
if carry_bits >= 3 {
100+
// we have a new full byte -- 3, 4 or 5 carry bits, plus 5 new ones
101+
// For combining with carry '|', '^', or '+' can be used (disjoint bit positions)
102+
let next = carry + (curr_in_as_u8 << carry_bits);
103+
output.push(next);
104+
carry = curr_in_as_u8 >> (8 - carry_bits);
105+
carry_bits -= 3; // added 5, removed 8
106+
} else {
107+
// only 0, 1, or 2 carry bits, plus 5 new ones
108+
carry += curr_in_as_u8 << carry_bits;
109+
carry_bits += 5;
79110
}
80111
}
81-
// Trim the highest feature bits.
82-
while !res_bytes.is_empty() && res_bytes[res_bytes.len() - 1] == 0 {
83-
res_bytes.pop();
112+
// No more inputs, output remaining (if any)
113+
if carry_bits > 0 {
114+
output.push(carry);
84115
}
85-
Ok(Bolt11InvoiceFeatures::from_le_bytes(res_bytes))
116+
// Trim the highest feature bits
117+
while !output.is_empty() && output[output.len() - 1] == 0 {
118+
output.pop();
119+
}
120+
Ok(Bolt11InvoiceFeatures::from_le_bytes(output))
86121
}
87122
}
88123

@@ -342,7 +377,7 @@ impl FromStr for SignedRawBolt11Invoice {
342377
}
343378

344379
let raw_hrp: RawHrp = hrp.to_string().to_lowercase().parse()?;
345-
let data_part = RawDataPart::from_base32(&data[..data.len()-SIGNATURE_LEN5])?;
380+
let data_part = RawDataPart::from_base32(&data[..data.len() - SIGNATURE_LEN5])?;
346381

347382
Ok(SignedRawBolt11Invoice {
348383
raw_invoice: RawBolt11Invoice {
@@ -351,9 +386,9 @@ impl FromStr for SignedRawBolt11Invoice {
351386
},
352387
hash: RawBolt11Invoice::hash_from_parts(
353388
hrp.to_string().as_bytes(),
354-
&data[..data.len()-SIGNATURE_LEN5]
389+
&data[..data.len() - SIGNATURE_LEN5],
355390
),
356-
signature: Bolt11InvoiceSignature::from_base32(&data[data.len()-SIGNATURE_LEN5..])?,
391+
signature: Bolt11InvoiceSignature::from_base32(&data[data.len() - SIGNATURE_LEN5..])?,
357392
})
358393
}
359394
}
@@ -415,7 +450,11 @@ impl FromBase32 for PositiveTimestamp {
415450

416451
fn from_base32(b32: &[Fe32]) -> Result<Self, Self::Err> {
417452
if b32.len() != 7 {
418-
return Err(Bolt11ParseError::InvalidSliceLength("PositiveTimestamp::from_base32()".into()));
453+
return Err(Bolt11ParseError::InvalidSliceLength(
454+
b32.len(),
455+
7,
456+
"PositiveTimestamp::from_base32()".into(),
457+
));
419458
}
420459
let timestamp: u64 = parse_u64_be(b32)
421460
.expect("7*5bit < 64bit, no overflow possible");
@@ -430,7 +469,11 @@ impl FromBase32 for Bolt11InvoiceSignature {
430469
type Err = Bolt11ParseError;
431470
fn from_base32(signature: &[Fe32]) -> Result<Self, Self::Err> {
432471
if signature.len() != 104 {
433-
return Err(Bolt11ParseError::InvalidSliceLength("Bolt11InvoiceSignature::from_base32()".into()));
472+
return Err(Bolt11ParseError::InvalidSliceLength(
473+
signature.len(),
474+
104,
475+
"Bolt11InvoiceSignature::from_base32()".into(),
476+
));
434477
}
435478
let recoverable_signature_bytes = Vec::<u8>::from_base32(signature)?;
436479
let signature = &recoverable_signature_bytes[0..64];
@@ -483,7 +526,9 @@ fn parse_tagged_parts(data: &[Fe32]) -> Result<Vec<RawTaggedField>, Bolt11ParseE
483526
Ok(field) => {
484527
parts.push(RawTaggedField::KnownSemantics(field))
485528
},
486-
Err(Bolt11ParseError::Skip)|Err(Bolt11ParseError::Bech32Error(_)) => {
529+
Err(Bolt11ParseError::Skip)
530+
| Err(Bolt11ParseError::InvalidSliceLength(_, _, _))
531+
| Err(Bolt11ParseError::Bech32Error(_)) => {
487532
parts.push(RawTaggedField::UnknownSemantics(field.into()))
488533
},
489534
Err(e) => {return Err(e)}
@@ -688,9 +733,6 @@ impl Display for Bolt11ParseError {
688733
Bolt11ParseError::Bech32Error(ref e) => {
689734
write!(f, "Invalid bech32: {}", e)
690735
}
691-
Bolt11ParseError::GenericBech32Error => {
692-
write!(f, "Invalid bech32")
693-
}
694736
Bolt11ParseError::ParseAmountError(ref e) => {
695737
write!(f, "Invalid amount in hrp ({})", e)
696738
}
@@ -700,9 +742,13 @@ impl Display for Bolt11ParseError {
700742
Bolt11ParseError::DescriptionDecodeError(ref e) => {
701743
write!(f, "Description is not a valid utf-8 string: {}", e)
702744
}
703-
Bolt11ParseError::InvalidSliceLength(ref function) => {
704-
write!(f, "Slice in function {} had the wrong length", function)
705-
}
745+
Bolt11ParseError::InvalidSliceLength(ref len, ref expected, ref function) => {
746+
write!(
747+
f,
748+
"Slice had length {} instead of {} in function {}",
749+
len, expected, function
750+
)
751+
},
706752
Bolt11ParseError::BadPrefix => f.write_str("did not begin with 'ln'"),
707753
Bolt11ParseError::UnknownCurrency => f.write_str("currency code unknown"),
708754
Bolt11ParseError::UnknownSiPrefix => f.write_str("unknown SI prefix"),
@@ -767,9 +813,7 @@ from_error!(Bolt11ParseError::DescriptionDecodeError, str::Utf8Error);
767813

768814
impl From<CheckedHrpstringError> for Bolt11ParseError {
769815
fn from(e: CheckedHrpstringError) -> Self {
770-
match e {
771-
_ => Bolt11ParseError::Bech32Error(e)
772-
}
816+
Self::Bech32Error(e)
773817
}
774818
}
775819

lightning-invoice/src/lib.rs

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ extern crate serde;
3333
#[cfg(feature = "std")]
3434
use std::time::SystemTime;
3535

36-
use bech32::Fe32;
3736
use bech32::primitives::decode::CheckedHrpstringError;
37+
use bech32::Fe32;
3838
use bitcoin::{Address, Network, PubkeyHash, ScriptHash, WitnessProgram, WitnessVersion};
3939
use bitcoin::hashes::{Hash, sha256};
4040
use lightning_types::features::Bolt11InvoiceFeatures;
@@ -79,17 +79,16 @@ use crate::prelude::*;
7979

8080
/// Re-export serialization traits
8181
#[cfg(fuzzing)]
82-
pub use crate::ser::Base32Iterable;
83-
#[cfg(fuzzing)]
8482
pub use crate::de::FromBase32;
83+
#[cfg(fuzzing)]
84+
pub use crate::ser::Base32Iterable;
8585

8686
/// Errors that indicate what is wrong with the invoice. They have some granularity for debug
8787
/// reasons, but should generally result in an "invalid BOLT11 invoice" message for the user.
8888
#[allow(missing_docs)]
8989
#[derive(PartialEq, Eq, Debug, Clone)]
9090
pub enum Bolt11ParseError {
9191
Bech32Error(CheckedHrpstringError),
92-
GenericBech32Error,
9392
ParseAmountError(ParseIntError),
9493
MalformedSignature(bitcoin::secp256k1::Error),
9594
BadPrefix,
@@ -105,7 +104,8 @@ pub enum Bolt11ParseError {
105104
InvalidPubKeyHashLength,
106105
InvalidScriptHashLength,
107106
InvalidRecoveryId,
108-
InvalidSliceLength(String),
107+
// Invalid length, with actual length, expected length, and function info
108+
InvalidSliceLength(usize, usize, String),
109109

110110
/// Not an error, but used internally to signal that a part of the invoice should be ignored
111111
/// according to BOLT11
@@ -439,10 +439,18 @@ impl PartialOrd for RawTaggedField {
439439
impl Ord for RawTaggedField {
440440
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
441441
match (self, other) {
442-
(RawTaggedField::KnownSemantics(ref a), RawTaggedField::KnownSemantics(ref b)) => a.cmp(b),
443-
(RawTaggedField::UnknownSemantics(ref a), RawTaggedField::UnknownSemantics(ref b)) => a.iter().map(|a| a.to_u8()).cmp(b.iter().map(|b| b.to_u8())),
444-
(RawTaggedField::KnownSemantics(..), RawTaggedField::UnknownSemantics(..)) => core::cmp::Ordering::Less,
445-
(RawTaggedField::UnknownSemantics(..), RawTaggedField::KnownSemantics(..)) => core::cmp::Ordering::Greater,
442+
(RawTaggedField::KnownSemantics(ref a), RawTaggedField::KnownSemantics(ref b)) => {
443+
a.cmp(b)
444+
},
445+
(RawTaggedField::UnknownSemantics(ref a), RawTaggedField::UnknownSemantics(ref b)) => {
446+
a.iter().map(|a| a.to_u8()).cmp(b.iter().map(|b| b.to_u8()))
447+
},
448+
(RawTaggedField::KnownSemantics(..), RawTaggedField::UnknownSemantics(..)) => {
449+
core::cmp::Ordering::Less
450+
},
451+
(RawTaggedField::UnknownSemantics(..), RawTaggedField::KnownSemantics(..)) => {
452+
core::cmp::Ordering::Greater
453+
},
446454
}
447455
}
448456
}

0 commit comments

Comments
 (0)