Skip to content

Commit

Permalink
Own implementation of Bech32::u5 type, upgrade of bech32 dependency
Browse files Browse the repository at this point in the history
  • Loading branch information
optout21 committed Jul 23, 2024
1 parent 78c0eaa commit e586562
Show file tree
Hide file tree
Showing 20 changed files with 712 additions and 95 deletions.
2 changes: 1 addition & 1 deletion fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ stdin_fuzz = []
lightning = { path = "../lightning", features = ["regex", "hashbrown", "_test_utils"] }
lightning-invoice = { path = "../lightning-invoice" }
lightning-rapid-gossip-sync = { path = "../lightning-rapid-gossip-sync" }
bech32 = "0.9.1"
bech32 = "0.11.0"
bitcoin = { version = "0.31.2", features = ["secp-lowmemory"] }
hex = { package = "hex-conservative", version = "0.1.1", default-features = false }

Expand Down
2 changes: 1 addition & 1 deletion fuzz/src/bolt11_deser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
// licenses.

use crate::utils::test_logger;
use bech32::{u5, FromBase32, ToBase32};
use bitcoin::secp256k1::{Secp256k1, SecretKey};
use lightning::bech32::{u5, FromBase32, ToBase32};
use lightning_invoice::{
Bolt11Invoice, RawBolt11Invoice, RawDataPart, RawHrp, RawTaggedField, TaggedField,
};
Expand Down
2 changes: 1 addition & 1 deletion fuzz/src/chanmon_consistency.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ use bitcoin::hashes::sha256d::Hash as Sha256dHash;
use bitcoin::hashes::Hash as TraitImport;
use bitcoin::WPubkeyHash;

use lightning::bech32::u5;
use lightning::blinded_path::message::MessageContext;
use lightning::blinded_path::payment::ReceiveTlvs;
use lightning::blinded_path::BlindedPath;
Expand Down Expand Up @@ -79,7 +80,6 @@ use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature};
use bitcoin::secp256k1::schnorr;
use bitcoin::secp256k1::{self, Message, PublicKey, Scalar, Secp256k1, SecretKey};

use bech32::u5;
use std::cmp::{self, Ordering};
use std::io::Cursor;
use std::mem;
Expand Down
2 changes: 1 addition & 1 deletion fuzz/src/full_stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ use bitcoin::hashes::sha256d::Hash as Sha256dHash;
use bitcoin::hashes::Hash as _;
use bitcoin::WPubkeyHash;

use lightning::bech32::u5;
use lightning::blinded_path::message::MessageContext;
use lightning::blinded_path::payment::ReceiveTlvs;
use lightning::blinded_path::BlindedPath;
Expand Down Expand Up @@ -76,7 +77,6 @@ use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature};
use bitcoin::secp256k1::schnorr;
use bitcoin::secp256k1::{self, Message, PublicKey, Scalar, Secp256k1, SecretKey};

use bech32::u5;
use std::cell::RefCell;
use std::cmp;
use std::convert::TryInto;
Expand Down
2 changes: 1 addition & 1 deletion fuzz/src/onion_message.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// Imports that need to be added manually
use bech32::u5;
use bitcoin::blockdata::script::ScriptBuf;
use bitcoin::secp256k1::ecdh::SharedSecret;
use bitcoin::secp256k1::ecdsa::RecoverableSignature;
use bitcoin::secp256k1::schnorr;
use bitcoin::secp256k1::{self, PublicKey, Scalar, Secp256k1, SecretKey};

use lightning::bech32::u5;
use lightning::blinded_path::message::{MessageContext, OffersContext};
use lightning::blinded_path::{BlindedPath, EmptyNodeIdLookUp};
use lightning::ln::features::InitFeatures;
Expand Down
2 changes: 1 addition & 1 deletion lightning-invoice/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ no-std = ["lightning/no-std"]
std = ["bitcoin/std", "lightning/std", "bech32/std"]

[dependencies]
bech32 = { version = "0.9.1", default-features = false }
bech32 = { version = "0.11.0", default-features = false }
lightning = { version = "0.0.123-beta", path = "../lightning", default-features = false }
secp256k1 = { version = "0.28.0", default-features = false, features = ["recovery", "alloc"] }
serde = { version = "1.0.118", optional = true }
Expand Down
64 changes: 35 additions & 29 deletions lightning-invoice/src/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ use core::num::ParseIntError;
use core::str;
use core::str::FromStr;

use bech32::{u5, FromBase32};
use bech32::Bech32;
use bech32::primitives::decode::{CheckedHrpstring, CheckedHrpstringError};

use bitcoin::{PubkeyHash, ScriptHash, WitnessVersion};
use bitcoin::hashes::Hash;
use bitcoin::hashes::sha256;
use crate::prelude::*;
use lightning::bech32::{Bech32Error, FromBase32, u5};
use lightning::ln::types::PaymentSecret;
use lightning::routing::gossip::RoutingFees;
use lightning::routing::router::{RouteHint, RouteHintHop};
Expand Down Expand Up @@ -270,31 +272,32 @@ impl FromStr for SignedRawBolt11Invoice {
type Err = Bolt11ParseError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let (hrp, data, var) = bech32::decode(s)?;

if var == bech32::Variant::Bech32m {
// Consider Bech32m addresses to be "Invalid Checksum", since that is what we'd get if
// we didn't support Bech32m (which lightning does not use).
return Err(Bolt11ParseError::Bech32Error(bech32::Error::InvalidChecksum));
}
let parsed = CheckedHrpstring::new::<Bech32>(s)?;
let hrp = parsed.hrp();
// access the parse data part as chars, covert to u5
let data: Vec<_> = parsed.data_part_ascii_no_checksum().iter()
.map(|ch| u5::try_from_char(char::from(*ch)).expect("value should be < 32"))
.collect();

if data.len() < 104 {
const MIN_LEN: usize = 104;
if data.len() < MIN_LEN {
return Err(Bolt11ParseError::TooShortDataPart);
}

let raw_hrp: RawHrp = hrp.parse()?;
let data_part = RawDataPart::from_base32(&data[..data.len()-104])?;
let raw_hrp: RawHrp = hrp.to_string().to_lowercase().parse()?;
let data_part = RawDataPart::from_base32(&data[..data.len()-MIN_LEN])?;

Ok(SignedRawBolt11Invoice {
raw_invoice: RawBolt11Invoice {
hrp: raw_hrp,
data: data_part,
},
hash: RawBolt11Invoice::hash_from_parts(
hrp.as_bytes(),
&data[..data.len()-104]
hrp.to_string().as_bytes(),
&data[..data.len()-MIN_LEN]
),
signature: Bolt11InvoiceSignature::from_base32(&data[data.len()-104..])?,
signature: Bolt11InvoiceSignature::from_base32(&data[data.len()-MIN_LEN..])?,
})
}
}
Expand Down Expand Up @@ -389,7 +392,7 @@ macro_rules! define_parse_int_be { ($name: ident, $ty: ty) => {
digits.iter().fold(Some(Default::default()), |acc, b|
acc
.and_then(|x| x.checked_mul(32))
.and_then(|x| x.checked_add((Into::<u8>::into(*b)).into()))
.and_then(|x| x.checked_add((*b).as_u8().into()))
)
}
} }
Expand Down Expand Up @@ -424,7 +427,7 @@ fn parse_tagged_parts(data: &[u5]) -> Result<Vec<RawTaggedField>, Bolt11ParseErr
Ok(field) => {
parts.push(RawTaggedField::KnownSemantics(field))
},
Err(Bolt11ParseError::Skip)|Err(Bolt11ParseError::Bech32Error(bech32::Error::InvalidLength)) => {
Err(Bolt11ParseError::Skip)|Err(Bolt11ParseError::Bech32Error(_)) => {
parts.push(RawTaggedField::UnknownSemantics(field.into()))
},
Err(e) => {return Err(e)}
Expand All @@ -444,7 +447,7 @@ impl FromBase32 for TaggedField {
let tag = field[0];
let field_data = &field[3..];

match tag.to_u8() {
match tag.as_u8() {
constants::TAG_PAYMENT_HASH =>
Ok(TaggedField::PaymentHash(Sha256::from_base32(field_data)?)),
constants::TAG_DESCRIPTION =>
Expand Down Expand Up @@ -550,7 +553,7 @@ impl FromBase32 for Fallback {
return Err(Bolt11ParseError::UnexpectedEndOfTaggedFields);
}

let version = field_data[0].to_u8();
let version = field_data[0].as_u8();
let bytes = Vec::<u8>::from_base32(&field_data[1..])?;

match version {
Expand Down Expand Up @@ -629,6 +632,9 @@ impl Display for Bolt11ParseError {
Bolt11ParseError::Bech32Error(ref e) => {
write!(f, "Invalid bech32: {}", e)
}
Bolt11ParseError::Bech32ExternalError(ref e) => {
write!(f, "Invalid bech32: {}", e)
}
Bolt11ParseError::ParseAmountError(ref e) => {
write!(f, "Invalid amount in hrp ({})", e)
}
Expand Down Expand Up @@ -703,10 +709,17 @@ from_error!(Bolt11ParseError::MalformedSignature, secp256k1::Error);
from_error!(Bolt11ParseError::ParseAmountError, ParseIntError);
from_error!(Bolt11ParseError::DescriptionDecodeError, str::Utf8Error);

impl From<bech32::Error> for Bolt11ParseError {
fn from(e: bech32::Error) -> Self {
impl From<CheckedHrpstringError> for Bolt11ParseError {
fn from(e: CheckedHrpstringError) -> Self {
match e {
_ => Bolt11ParseError::Bech32ExternalError(e)
}
}
}

impl From<Bech32Error> for Bolt11ParseError {
fn from(e: Bech32Error) -> Self {
match e {
bech32::Error::InvalidPadding => Bolt11ParseError::PaddingError,
_ => Bolt11ParseError::Bech32Error(e)
}
}
Expand All @@ -726,9 +739,9 @@ impl From<crate::Bolt11SemanticError> for ParseOrSemanticError {

#[cfg(test)]
mod test {
use super::{u5, FromBase32};
use crate::de::Bolt11ParseError;
use secp256k1::PublicKey;
use bech32::u5;
use bitcoin::hashes::sha256;
use std::str::FromStr;

Expand Down Expand Up @@ -779,7 +792,6 @@ mod test {
#[test]
fn test_parse_sha256_hash() {
use crate::Sha256;
use bech32::FromBase32;

let input = from_bech32(
"qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypq".as_bytes()
Expand All @@ -802,7 +814,6 @@ mod test {
#[test]
fn test_parse_description() {
use crate::Description;
use bech32::FromBase32;

let input = from_bech32("xysxxatsyp3k7enxv4js".as_bytes());
let expected = Ok(Description::new("1 cup coffee".to_owned()).unwrap());
Expand All @@ -812,7 +823,6 @@ mod test {
#[test]
fn test_parse_payee_pub_key() {
use crate::PayeePubKey;
use bech32::FromBase32;

let input = from_bech32("q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66".as_bytes());
let pk_bytes = [
Expand All @@ -836,7 +846,6 @@ mod test {
#[test]
fn test_parse_expiry_time() {
use crate::ExpiryTime;
use bech32::FromBase32;

let input = from_bech32("pu".as_bytes());
let expected = Ok(ExpiryTime::from_seconds(60));
Expand All @@ -849,7 +858,6 @@ mod test {
#[test]
fn test_parse_min_final_cltv_expiry_delta() {
use crate::MinFinalCltvExpiryDelta;
use bech32::FromBase32;

let input = from_bech32("pr".as_bytes());
let expected = Ok(MinFinalCltvExpiryDelta(35));
Expand All @@ -860,7 +868,6 @@ mod test {
#[test]
fn test_parse_fallback() {
use crate::Fallback;
use bech32::FromBase32;
use bitcoin::{PubkeyHash, ScriptHash, WitnessVersion};
use bitcoin::hashes::Hash;

Expand Down Expand Up @@ -921,7 +928,6 @@ mod test {
use lightning::routing::gossip::RoutingFees;
use lightning::routing::router::{RouteHint, RouteHintHop};
use crate::PrivateRoute;
use bech32::FromBase32;

let input = from_bech32(
"q20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvpeuqa\
Expand Down Expand Up @@ -967,7 +973,7 @@ mod test {
assert_eq!(PrivateRoute::from_base32(&input), Ok(PrivateRoute(RouteHint(expected))));

assert_eq!(
PrivateRoute::from_base32(&[u5::try_from_u8(0).unwrap(); 40][..]),
PrivateRoute::from_base32(&[u5::ZERO; 40][..]),
Err(Bolt11ParseError::UnexpectedEndOfTaggedFields)
);
}
Expand Down
8 changes: 4 additions & 4 deletions lightning-invoice/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,11 @@ extern crate serde;
#[cfg(feature = "std")]
use std::time::SystemTime;

use bech32::u5;
use bech32::primitives::decode::CheckedHrpstringError;
use bitcoin::{Address, Network, PubkeyHash, ScriptHash, WitnessProgram, WitnessVersion};
use bitcoin::address::Payload;
use bitcoin::hashes::{Hash, sha256};
use lightning::bech32::{Bech32Error, u5, ToBase32};
use lightning::ln::features::Bolt11InvoiceFeatures;
use lightning::util::invoice::construct_invoice_preimage;

Expand Down Expand Up @@ -90,7 +91,8 @@ use crate::prelude::*;
#[allow(missing_docs)]
#[derive(PartialEq, Eq, Debug, Clone)]
pub enum Bolt11ParseError {
Bech32Error(bech32::Error),
Bech32Error(Bech32Error),
Bech32ExternalError(CheckedHrpstringError),
ParseAmountError(ParseIntError),
MalformedSignature(secp256k1::Error),
BadPrefix,
Expand Down Expand Up @@ -979,8 +981,6 @@ impl RawBolt11Invoice {

/// Calculate the hash of the encoded `RawBolt11Invoice` which should be signed.
pub fn signable_hash(&self) -> [u8; 32] {
use bech32::ToBase32;

RawBolt11Invoice::hash_from_parts(
self.hrp.to_string().as_bytes(),
&self.data.to_base32()
Expand Down
13 changes: 8 additions & 5 deletions lightning-invoice/src/ser.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use lightning::bech32::{Base32Len, ToBase32, u5, WriteBase32};
use bech32::{Bech32, Fe32, Fe32IterExt, Hrp};
use core::fmt;
use core::fmt::{Display, Formatter};
use bech32::{ToBase32, u5, WriteBase32, Base32Len};
use crate::prelude::*;

use super::{Bolt11Invoice, Sha256, TaggedField, ExpiryTime, MinFinalCltvExpiryDelta, Fallback, PayeePubKey, Bolt11InvoiceSignature, PositiveTimestamp,
Expand Down Expand Up @@ -118,7 +119,10 @@ impl Display for SignedRawBolt11Invoice {
let mut data = self.raw_invoice.data.to_base32();
data.extend_from_slice(&self.signature.to_base32());

bech32::encode_to_fmt(f, &hrp, data, bech32::Variant::Bech32).expect("HRP is valid")?;
// TODO(bech32): support with_checksum() in own u5 implementation
let bech32 = data.iter().map(|u| Fe32::try_from(u.as_u8()).expect("<31"))
.with_checksum::<Bech32>(&Hrp::parse(&hrp).expect("not a valid hrp string")).chars().collect::<String>();
f.write_str(&bech32)?;

Ok(())
}
Expand Down Expand Up @@ -468,8 +472,6 @@ impl ToBase32 for Bolt11InvoiceSignature {

#[cfg(test)]
mod test {
use bech32::CheckBase32;

#[test]
fn test_currency_code() {
use crate::Currency;
Expand Down Expand Up @@ -497,9 +499,10 @@ mod test {
#[test]
fn test_encode_int_be_base32() {
use crate::ser::encode_int_be_base32;
use lightning::bech32::u5;

let input: u64 = 33764;
let expected_out = CheckBase32::check_base32(&[1, 0, 31, 4]).unwrap();
let expected_out = [1u8, 0, 31, 4].iter().map(|v| u5::try_from_u8(*v).unwrap()).collect::<Vec<u5>>();

assert_eq!(expected_out, encode_int_be_base32(input));
}
Expand Down
2 changes: 1 addition & 1 deletion lightning-invoice/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
use crate::{Bolt11Invoice, CreationError, Currency, InvoiceBuilder, SignOrCreationError};

use crate::{prelude::*, Description, Bolt11InvoiceDescription, Sha256};
use bech32::ToBase32;
use bitcoin::hashes::Hash;
use lightning::bech32::ToBase32;
use lightning::chain;
use lightning::chain::chaininterface::{BroadcasterInterface, FeeEstimator};
use lightning::sign::{Recipient, NodeSigner, SignerProvider, EntropySource};
Expand Down
7 changes: 4 additions & 3 deletions lightning-invoice/tests/ser_de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ extern crate lightning_invoice;
extern crate secp256k1;
extern crate hex;

use bech32::primitives::decode::{CharError, CheckedHrpstringError, ChecksumError, UncheckedHrpstringError};
use bitcoin::{PubkeyHash, ScriptHash, WitnessVersion};
use bitcoin::hashes::hex::FromHex;
use bitcoin::hashes::{sha256, Hash};
Expand Down Expand Up @@ -419,13 +420,13 @@ fn test_bolt_invalid_invoices() {
), Err(ParseOrSemanticError::SemanticError(Bolt11SemanticError::InvalidFeatures)));
assert_eq!(Bolt11Invoice::from_str(
"lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpquwpc4curk03c9wlrswe78q4eyqc7d8d0xqzpuyk0sg5g70me25alkluzd2x62aysf2pyy8edtjeevuv4p2d5p76r4zkmneet7uvyakky2zr4cusd45tftc9c5fh0nnqpnl2jfll544esqchsrnt"
), Err(ParseOrSemanticError::ParseError(Bolt11ParseError::Bech32Error(bech32::Error::InvalidChecksum))));
), Err(ParseOrSemanticError::ParseError(Bolt11ParseError::Bech32ExternalError(CheckedHrpstringError::Checksum(ChecksumError::InvalidResidue)))));
assert_eq!(Bolt11Invoice::from_str(
"pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpquwpc4curk03c9wlrswe78q4eyqc7d8d0xqzpuyk0sg5g70me25alkluzd2x62aysf2pyy8edtjeevuv4p2d5p76r4zkmneet7uvyakky2zr4cusd45tftc9c5fh0nnqpnl2jfll544esqchsrny"
), Err(ParseOrSemanticError::ParseError(Bolt11ParseError::Bech32Error(bech32::Error::MissingSeparator))));
), Err(ParseOrSemanticError::ParseError(Bolt11ParseError::Bech32ExternalError(CheckedHrpstringError::Parse(UncheckedHrpstringError::Char(CharError::MissingSeparator))))));
assert_eq!(Bolt11Invoice::from_str(
"LNBC2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpquwpc4curk03c9wlrswe78q4eyqc7d8d0xqzpuyk0sg5g70me25alkluzd2x62aysf2pyy8edtjeevuv4p2d5p76r4zkmneet7uvyakky2zr4cusd45tftc9c5fh0nnqpnl2jfll544esqchsrny"
), Err(ParseOrSemanticError::ParseError(Bolt11ParseError::Bech32Error(bech32::Error::MixedCase))));
), Err(ParseOrSemanticError::ParseError(Bolt11ParseError::Bech32ExternalError(CheckedHrpstringError::Parse(UncheckedHrpstringError::Char(CharError::MixedCase))))));
assert_eq!(Bolt11Invoice::from_str(
"lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpusp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9qrsgqwgt7mcn5yqw3yx0w94pswkpq6j9uh6xfqqqtsk4tnarugeektd4hg5975x9am52rz4qskukxdmjemg92vvqz8nvmsye63r5ykel43pgz7zq0g2"
), Err(ParseOrSemanticError::SemanticError(Bolt11SemanticError::InvalidSignature)));
Expand Down
2 changes: 1 addition & 1 deletion lightning/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ grind_signatures = []
default = ["std", "grind_signatures"]

[dependencies]
bech32 = { version = "0.9.1", default-features = false }
bech32 = { version = "0.11.0", default-features = false }
bitcoin = { version = "0.31.2", default-features = false, features = ["secp-recovery"] }

hashbrown = { version = "0.13", optional = true, default-features = false }
Expand Down
Loading

0 comments on commit e586562

Please sign in to comment.