Skip to content

Upgrade bech32 dependency #3181

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion fuzz/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 }

7 changes: 4 additions & 3 deletions fuzz/src/bolt11_deser.rs
Original file line number Diff line number Diff line change
@@ -8,10 +8,11 @@
// licenses.

use crate::utils::test_logger;
use bech32::{u5, FromBase32, ToBase32};
use bech32::Fe32;
use bitcoin::secp256k1::{Secp256k1, SecretKey};
use lightning_invoice::{
Bolt11Invoice, RawBolt11Invoice, RawDataPart, RawHrp, RawTaggedField, TaggedField,
Bolt11Invoice, FromBase32, RawBolt11Invoice, RawDataPart, RawHrp, RawTaggedField, TaggedField,
ToBase32,
};
use std::str::FromStr;

@@ -25,7 +26,7 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], _out: Out) {
Err(_) => return,
};
let bech32 =
data.iter().skip(hrp_len).map(|x| u5::try_from_u8(x % 32).unwrap()).collect::<Vec<_>>();
data.iter().skip(hrp_len).map(|x| Fe32::try_from(x % 32).unwrap()).collect::<Vec<_>>();
let invoice_data = match RawDataPart::from_base32(&bech32) {
Ok(invoice) => invoice,
Err(_) => return,
4 changes: 2 additions & 2 deletions fuzz/src/chanmon_consistency.rs
Original file line number Diff line number Diff line change
@@ -56,6 +56,7 @@ use lightning::ln::msgs::{
self, ChannelMessageHandler, CommitmentUpdate, DecodeError, Init, UpdateAddHTLC,
};
use lightning::ln::script::ShutdownScript;
use lightning::ln::types::InvoiceData;
use lightning::ln::{ChannelId, PaymentHash, PaymentPreimage, PaymentSecret};
use lightning::offers::invoice::{BlindedPayInfo, UnsignedBolt12Invoice};
use lightning::offers::invoice_request::UnsignedInvoiceRequest;
@@ -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;
@@ -332,7 +332,7 @@ impl NodeSigner for KeyProvider {
}

fn sign_invoice(
&self, _hrp_bytes: &[u8], _invoice_data: &[u5], _recipient: Recipient,
&self, _hrp_bytes: &[u8], _invoice_data: &InvoiceData, _recipient: Recipient,
) -> Result<RecoverableSignature, ()> {
unreachable!()
}
4 changes: 2 additions & 2 deletions fuzz/src/full_stack.rs
Original file line number Diff line number Diff line change
@@ -49,6 +49,7 @@ use lightning::ln::peer_handler::{
IgnoringMessageHandler, MessageHandler, PeerManager, SocketDescriptor,
};
use lightning::ln::script::ShutdownScript;
use lightning::ln::types::InvoiceData;
use lightning::ln::{ChannelId, PaymentHash, PaymentPreimage, PaymentSecret};
use lightning::offers::invoice::{BlindedPayInfo, UnsignedBolt12Invoice};
use lightning::offers::invoice_request::UnsignedInvoiceRequest;
@@ -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;
@@ -406,7 +406,7 @@ impl NodeSigner for KeyProvider {
}

fn sign_invoice(
&self, _hrp_bytes: &[u8], _invoice_data: &[u5], _recipient: Recipient,
&self, _hrp_bytes: &[u8], _invoice_data: &InvoiceData, _recipient: Recipient,
) -> Result<RecoverableSignature, ()> {
unreachable!()
}
4 changes: 2 additions & 2 deletions fuzz/src/onion_message.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// 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;
@@ -11,6 +10,7 @@ use lightning::blinded_path::{BlindedPath, EmptyNodeIdLookUp};
use lightning::ln::features::InitFeatures;
use lightning::ln::msgs::{self, DecodeError, OnionMessageHandler};
use lightning::ln::script::ShutdownScript;
use lightning::ln::types::InvoiceData;
use lightning::offers::invoice::UnsignedBolt12Invoice;
use lightning::offers::invoice_request::UnsignedInvoiceRequest;
use lightning::onion_message::async_payments::{
@@ -224,7 +224,7 @@ impl NodeSigner for KeyProvider {
}

fn sign_invoice(
&self, _hrp_bytes: &[u8], _invoice_data: &[u5], _recipient: Recipient,
&self, _hrp_bytes: &[u8], _invoice_data: &InvoiceData, _recipient: Recipient,
) -> Result<RecoverableSignature, ()> {
unreachable!()
}
2 changes: 1 addition & 1 deletion lightning-invoice/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 }
148 changes: 97 additions & 51 deletions lightning-invoice/src/de.rs
Original file line number Diff line number Diff line change
@@ -8,7 +8,8 @@ use core::num::ParseIntError;
use core::str;
use core::str::FromStr;

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

use bitcoin::{PubkeyHash, ScriptHash, WitnessVersion};
use bitcoin::hashes::Hash;
@@ -23,10 +24,59 @@ use 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};
constants, SignedRawBolt11Invoice, RawDataPart, Bolt11InvoiceFeatures, FromBase32};

use self::hrp_sm::parse_hrp;

impl FromBase32 for Vec<u8> {
type Err = CheckedHrpstringError;

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

impl FromBase32 for PaymentSecret {
type Err = CheckedHrpstringError;

fn from_base32(field_data: &[Fe32]) -> Result<PaymentSecret, CheckedHrpstringError> {
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))
}
}
}

impl FromBase32 for Bolt11InvoiceFeatures {
type Err = CheckedHrpstringError;

fn from_base32(field_data: &[Fe32]) -> Result<Bolt11InvoiceFeatures, CheckedHrpstringError> {
// 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(Bolt11InvoiceFeatures::from_le_bytes(res_bytes))
}
}

/// State machine to parse the hrp
mod hrp_sm {
use core::ops::Range;
@@ -270,31 +320,31 @@ 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();
let data: Vec<_> = parsed.data_part_ascii_no_checksum().iter()
.map(|ch| Fe32::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..])?,
})
}
}
@@ -336,7 +386,7 @@ impl FromStr for RawHrp {
impl FromBase32 for RawDataPart {
type Err = Bolt11ParseError;

fn from_base32(data: &[u5]) -> Result<Self, Self::Err> {
fn from_base32(data: &[Fe32]) -> Result<Self, Self::Err> {
if data.len() < 7 { // timestamp length
return Err(Bolt11ParseError::TooShortDataPart);
}
@@ -354,7 +404,7 @@ impl FromBase32 for RawDataPart {
impl FromBase32 for PositiveTimestamp {
type Err = Bolt11ParseError;

fn from_base32(b32: &[u5]) -> Result<Self, Self::Err> {
fn from_base32(b32: &[Fe32]) -> Result<Self, Self::Err> {
if b32.len() != 7 {
return Err(Bolt11ParseError::InvalidSliceLength("PositiveTimestamp::from_base32()".into()));
}
@@ -369,7 +419,7 @@ impl FromBase32 for PositiveTimestamp {

impl FromBase32 for Bolt11InvoiceSignature {
type Err = Bolt11ParseError;
fn from_base32(signature: &[u5]) -> Result<Self, Self::Err> {
fn from_base32(signature: &[Fe32]) -> Result<Self, Self::Err> {
if signature.len() != 104 {
return Err(Bolt11ParseError::InvalidSliceLength("Bolt11InvoiceSignature::from_base32()".into()));
}
@@ -385,7 +435,7 @@ impl FromBase32 for Bolt11InvoiceSignature {
}

macro_rules! define_parse_int_be { ($name: ident, $ty: ty) => {
fn $name(digits: &[u5]) -> Option<$ty> {
fn $name(digits: &[Fe32]) -> Option<$ty> {
digits.iter().fold(Some(Default::default()), |acc, b|
acc
.and_then(|x| x.checked_mul(32))
@@ -396,7 +446,7 @@ macro_rules! define_parse_int_be { ($name: ident, $ty: ty) => {
define_parse_int_be!(parse_u16_be, u16);
define_parse_int_be!(parse_u64_be, u64);

fn parse_tagged_parts(data: &[u5]) -> Result<Vec<RawTaggedField>, Bolt11ParseError> {
fn parse_tagged_parts(data: &[Fe32]) -> Result<Vec<RawTaggedField>, Bolt11ParseError> {
let mut parts = Vec::<RawTaggedField>::new();
let mut data = data;

@@ -424,8 +474,8 @@ 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)) => {
parts.push(RawTaggedField::UnknownSemantics(field.into()))
Err(Bolt11ParseError::Skip) | Err(Bolt11ParseError::Bech32Error(_)) => {
parts.push(RawTaggedField::UnknownSemantics(field.iter().map(|v| crate::Fe32Ord(*v)).collect()))
},
Err(e) => {return Err(e)}
}
@@ -436,7 +486,7 @@ fn parse_tagged_parts(data: &[u5]) -> Result<Vec<RawTaggedField>, Bolt11ParseErr
impl FromBase32 for TaggedField {
type Err = Bolt11ParseError;

fn from_base32(field: &[u5]) -> Result<TaggedField, Bolt11ParseError> {
fn from_base32(field: &[Fe32]) -> Result<TaggedField, Bolt11ParseError> {
if field.len() < 3 {
return Err(Bolt11ParseError::UnexpectedEndOfTaggedFields);
}
@@ -478,7 +528,7 @@ impl FromBase32 for TaggedField {
impl FromBase32 for Sha256 {
type Err = Bolt11ParseError;

fn from_base32(field_data: &[u5]) -> Result<Sha256, Bolt11ParseError> {
fn from_base32(field_data: &[Fe32]) -> Result<Sha256, Bolt11ParseError> {
if field_data.len() != 52 {
// "A reader MUST skip over […] a p, [or] h […] field that does not have data_length 52 […]."
Err(Bolt11ParseError::Skip)
@@ -492,7 +542,7 @@ impl FromBase32 for Sha256 {
impl FromBase32 for Description {
type Err = Bolt11ParseError;

fn from_base32(field_data: &[u5]) -> Result<Description, Bolt11ParseError> {
fn from_base32(field_data: &[Fe32]) -> Result<Description, Bolt11ParseError> {
let bytes = Vec::<u8>::from_base32(field_data)?;
let description = String::from(str::from_utf8(&bytes)?);
Ok(Description::new(description).expect(
@@ -504,7 +554,7 @@ impl FromBase32 for Description {
impl FromBase32 for PayeePubKey {
type Err = Bolt11ParseError;

fn from_base32(field_data: &[u5]) -> Result<PayeePubKey, Bolt11ParseError> {
fn from_base32(field_data: &[Fe32]) -> Result<PayeePubKey, Bolt11ParseError> {
if field_data.len() != 53 {
// "A reader MUST skip over […] a n […] field that does not have data_length 53 […]."
Err(Bolt11ParseError::Skip)
@@ -519,7 +569,7 @@ impl FromBase32 for PayeePubKey {
impl FromBase32 for ExpiryTime {
type Err = Bolt11ParseError;

fn from_base32(field_data: &[u5]) -> Result<ExpiryTime, Bolt11ParseError> {
fn from_base32(field_data: &[Fe32]) -> Result<ExpiryTime, Bolt11ParseError> {
match parse_u64_be(field_data)
.map(ExpiryTime::from_seconds)
{
@@ -532,7 +582,7 @@ impl FromBase32 for ExpiryTime {
impl FromBase32 for MinFinalCltvExpiryDelta {
type Err = Bolt11ParseError;

fn from_base32(field_data: &[u5]) -> Result<MinFinalCltvExpiryDelta, Bolt11ParseError> {
fn from_base32(field_data: &[Fe32]) -> Result<MinFinalCltvExpiryDelta, Bolt11ParseError> {
let expiry = parse_u64_be(field_data);
if let Some(expiry) = expiry {
Ok(MinFinalCltvExpiryDelta(expiry))
@@ -545,7 +595,7 @@ impl FromBase32 for MinFinalCltvExpiryDelta {
impl FromBase32 for Fallback {
type Err = Bolt11ParseError;

fn from_base32(field_data: &[u5]) -> Result<Fallback, Bolt11ParseError> {
fn from_base32(field_data: &[Fe32]) -> Result<Fallback, Bolt11ParseError> {
if field_data.is_empty() {
return Err(Bolt11ParseError::UnexpectedEndOfTaggedFields);
}
@@ -586,7 +636,7 @@ impl FromBase32 for Fallback {
impl FromBase32 for PrivateRoute {
type Err = Bolt11ParseError;

fn from_base32(field_data: &[u5]) -> Result<PrivateRoute, Bolt11ParseError> {
fn from_base32(field_data: &[Fe32]) -> Result<PrivateRoute, Bolt11ParseError> {
let bytes = Vec::<u8>::from_base32(field_data)?;

if bytes.len() % 51 != 0 {
@@ -629,6 +679,9 @@ impl Display for Bolt11ParseError {
Bolt11ParseError::Bech32Error(ref e) => {
write!(f, "Invalid bech32: {}", e)
}
Bolt11ParseError::GenericBech32Error => {
write!(f, "Invalid bech32")
}
Bolt11ParseError::ParseAmountError(ref e) => {
write!(f, "Invalid amount in hrp ({})", e)
}
@@ -703,10 +756,9 @@ 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 {
bech32::Error::InvalidPadding => Bolt11ParseError::PaddingError,
_ => Bolt11ParseError::Bech32Error(e)
}
}
@@ -726,9 +778,10 @@ impl From<crate::Bolt11SemanticError> for ParseOrSemanticError {

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

@@ -743,10 +796,10 @@ mod test {
1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1
];

fn from_bech32(bytes_5b: &[u8]) -> Vec<u5> {
fn from_bech32(bytes_5b: &[u8]) -> Vec<Fe32> {
bytes_5b
.iter()
.map(|c| u5::try_from_u8(CHARSET_REV[*c as usize] as u8).unwrap())
.map(|c| Fe32::try_from(CHARSET_REV[*c as usize] as u8).unwrap())
.collect()
}

@@ -767,19 +820,18 @@ mod test {
use crate::de::parse_u16_be;

assert_eq!(parse_u16_be(&[
u5::try_from_u8(1).unwrap(), u5::try_from_u8(2).unwrap(),
u5::try_from_u8(3).unwrap(), u5::try_from_u8(4).unwrap()]
Fe32::try_from(1).unwrap(), Fe32::try_from(2).unwrap(),
Fe32::try_from(3).unwrap(), Fe32::try_from(4).unwrap()]
), Some(34916));
assert_eq!(parse_u16_be(&[
u5::try_from_u8(2).unwrap(), u5::try_from_u8(0).unwrap(),
u5::try_from_u8(0).unwrap(), u5::try_from_u8(0).unwrap()]
Fe32::try_from(2).unwrap(), Fe32::try_from(0).unwrap(),
Fe32::try_from(0).unwrap(), Fe32::try_from(0).unwrap()]
), None);
}

#[test]
fn test_parse_sha256_hash() {
use crate::Sha256;
use bech32::FromBase32;

let input = from_bech32(
"qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypq".as_bytes()
@@ -802,7 +854,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());
@@ -812,7 +863,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 = [
@@ -836,7 +886,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));
@@ -849,7 +898,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));
@@ -860,7 +908,6 @@ mod test {
#[test]
fn test_parse_fallback() {
use crate::Fallback;
use bech32::FromBase32;
use bitcoin::{PubkeyHash, ScriptHash, WitnessVersion};
use bitcoin::hashes::Hash;

@@ -890,23 +937,23 @@ mod test {
})
),
(
vec![u5::try_from_u8(21).unwrap(); 41],
vec![Fe32::try_from(21).unwrap(); 41],
Err(Bolt11ParseError::Skip)
),
(
vec![],
Err(Bolt11ParseError::UnexpectedEndOfTaggedFields)
),
(
vec![u5::try_from_u8(1).unwrap(); 81],
vec![Fe32::try_from(1).unwrap(); 81],
Err(Bolt11ParseError::InvalidSegWitProgramLength)
),
(
vec![u5::try_from_u8(17).unwrap(); 1],
vec![Fe32::try_from(17).unwrap(); 1],
Err(Bolt11ParseError::InvalidPubKeyHashLength)
),
(
vec![u5::try_from_u8(18).unwrap(); 1],
vec![Fe32::try_from(18).unwrap(); 1],
Err(Bolt11ParseError::InvalidScriptHashLength)
)
];
@@ -921,7 +968,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\
@@ -967,7 +1013,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(&[Fe32::try_from(0).unwrap(); 40][..]),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

now that we have this InvoiceData wrapper around a [Fe32], do you figure it's worth also creating one for an individual value? Using finite field value constructors seems like it might have separation of concerns potential, but it's not that much different from what we'd been doing prior, just more explicit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's possible to have an own type (I would keep the u5 name), and only reuse the char conversion logic from the external bech32 crate. But event that is rather small logic, could be completely taken over.

Err(Bolt11ParseError::UnexpectedEndOfTaggedFields)
);
}
78 changes: 69 additions & 9 deletions lightning-invoice/src/lib.rs
Original file line number Diff line number Diff line change
@@ -41,11 +41,13 @@ extern crate serde;
#[cfg(feature = "std")]
use std::time::SystemTime;

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

use secp256k1::PublicKey;
@@ -85,12 +87,60 @@ 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>;
}


/// 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.
#[allow(missing_docs)]
#[derive(PartialEq, Eq, Debug, Clone)]
pub enum Bolt11ParseError {
Bech32Error(bech32::Error),
Bech32Error(CheckedHrpstringError),
GenericBech32Error,
ParseAmountError(ParseIntError),
MalformedSignature(secp256k1::Error),
BadPrefix,
@@ -414,6 +464,18 @@ impl From<Currency> for Network {
}
}

/// Wrapped Fe32 with Ord trait
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
pub struct Fe32Ord(pub Fe32);

impl Ord for Fe32Ord {
fn cmp(&self, other: &Self) -> Ordering { self.0.to_u8().cmp(&other.0.to_u8()) }
}

impl PartialOrd for Fe32Ord {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { self.0.to_u8().partial_cmp(&other.0.to_u8()) }
}

/// Tagged field which may have an unknown tag
///
/// This is not exported to bindings users as we don't currently support TaggedField
@@ -422,7 +484,7 @@ pub enum RawTaggedField {
/// Parsed tagged field with known tag
KnownSemantics(TaggedField),
/// tagged field which was not parsed due to an unknown tag or undefined field semantics
UnknownSemantics(Vec<u5>),
UnknownSemantics(Vec<Fe32Ord>),
}

/// Tagged field with known tag
@@ -970,17 +1032,15 @@ macro_rules! find_all_extract {
#[allow(missing_docs)]
impl RawBolt11Invoice {
/// Hash the HRP as bytes and signatureless data part.
fn hash_from_parts(hrp_bytes: &[u8], data_without_signature: &[u5]) -> [u8; 32] {
let preimage = construct_invoice_preimage(hrp_bytes, data_without_signature);
fn hash_from_parts(hrp_bytes: &[u8], data_without_signature: &[Fe32]) -> [u8; 32] {
let preimage = construct_invoice_preimage(hrp_bytes, &InvoiceData(data_without_signature.to_vec()));
let mut hash: [u8; 32] = Default::default();
hash.copy_from_slice(&sha256::Hash::hash(&preimage)[..]);
hash
}

/// 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()
@@ -1490,7 +1550,7 @@ impl From<TaggedField> for RawTaggedField {

impl TaggedField {
/// Numeric representation of the field's tag
pub fn tag(&self) -> u5 {
pub fn tag(&self) -> Fe32 {
let tag = match *self {
TaggedField::PaymentHash(_) => constants::TAG_PAYMENT_HASH,
TaggedField::Description(_) => constants::TAG_DESCRIPTION,
@@ -1505,7 +1565,7 @@ impl TaggedField {
TaggedField::Features(_) => constants::TAG_FEATURES,
};

u5::try_from_u8(tag).expect("all tags defined are <32")
Fe32::try_from(tag).expect("all tags defined are <32")
}
}

164 changes: 121 additions & 43 deletions lightning-invoice/src/ser.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,92 @@
use core::fmt;
use core::fmt::{Display, Formatter};
use bech32::{ToBase32, u5, WriteBase32, Base32Len};
use bech32::{Bech32, ByteIterExt, Fe32, Fe32IterExt, Hrp};
use crate::prelude::*;

use super::{Bolt11Invoice, Sha256, TaggedField, ExpiryTime, MinFinalCltvExpiryDelta, Fallback, PayeePubKey, Bolt11InvoiceSignature, PositiveTimestamp,
PrivateRoute, Description, RawTaggedField, Currency, RawHrp, SiPrefix, constants, SignedRawBolt11Invoice, RawDataPart};
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};

impl WriteBase32 for Vec<Fe32> {
type Err = ();

fn write(&mut self, data: &Vec<Fe32>) -> Result<(), Self::Err> {
self.extend_from_slice(data);
Ok(())
}

fn write_fe32(&mut self, data: Fe32) -> Result<(), Self::Err> {
self.push(data);
Ok(())
}
}

impl ToBase32 for Vec<u8> {
/// Encode as base32 and write it to the supplied writer
fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
writer.write(&self.iter().copied().bytes_to_fes().collect::<Vec<Fe32>>())
}
}

impl Base32Len for Vec<u8> {
/// Calculate the base32 serialized length
fn base32_len(&self) -> usize {
(self.len() * 8 + 7) / 5
}
}

impl ToBase32 for PaymentSecret {
fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
(&self.0[..]).to_vec().write_base32(writer)
}
}

impl Base32Len for PaymentSecret {
fn base32_len(&self) -> usize {
52
}
}

impl ToBase32 for Bolt11InvoiceFeatures {
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<Fe32> = vec![Fe32::Q; 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] = Fe32::try_from(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] = Fe32::try_from(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] = Fe32::try_from(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] == Fe32::Q {
res_u5s.remove(0);
}
writer.write(&res_u5s)
}
}

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 `u5`s resulting from the written bytes
/// 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
@@ -40,23 +116,23 @@ impl<'a, W: WriteBase32> BytesToBase32<'a, W> {
}

pub fn append_u8(&mut self, byte: u8) -> Result<(), W::Err> {
// Write first u5 if we have to write two u5s this round. That only happens if the
// 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_u5(
u5::try_from_u8((self.buffer & 0b11111000) >> 3 ).expect("<32")
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 u5. Save remaining bits from byte to buffer.
// 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_u5(u5::try_from_u8(from_buffer | from_byte).expect("<32"))?;
self.writer.write_fe32(Fe32::try_from(from_buffer | from_byte).expect("<32"))?;
self.buffer = byte << (5 - self.buffer_bits);
self.buffer_bits += 3;

@@ -70,17 +146,17 @@ impl<'a, W: WriteBase32> BytesToBase32<'a, W> {
}

fn inner_finalize(&mut self) -> Result<(), W::Err>{
// There can be at most two u5s left in the buffer after processing all bytes, write them.
// There can be at most two Fe32a left in the buffer after processing all bytes, write them.
if self.buffer_bits >= 5 {
self.writer.write_u5(
u5::try_from_u8((self.buffer & 0b11111000) >> 3).expect("<32")
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_u5(u5::try_from_u8(self.buffer >> 3).expect("<32"))?;
self.writer.write_fe32(Fe32::try_from(self.buffer >> 3).expect("<32"))?;
}

Ok(())
@@ -118,7 +194,8 @@ 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")?;
let bech32 = data.iter().copied().with_checksum::<Bech32>(&Hrp::parse(&hrp).expect("not a valid hrp string")).chars().collect::<String>();
f.write_str(&bech32)?;

Ok(())
}
@@ -173,14 +250,14 @@ impl Display for SiPrefix {
}
}

fn encode_int_be_base32(int: u64) -> Vec<u5> {
fn encode_int_be_base32(int: u64) -> Vec<Fe32> {
let base = 32u64;

let mut out_vec = Vec::<u5>::new();
let mut out_vec = Vec::<Fe32>::new();

let mut rem_int = int;
while rem_int != 0 {
out_vec.push(u5::try_from_u8((rem_int % base) as u8).expect("always <32"));
out_vec.push(Fe32::try_from((rem_int % base) as u8).expect("always <32"));
rem_int /= base;
}

@@ -214,16 +291,18 @@ fn encode_int_be_base256<T: Into<u64>>(int: T) -> Vec<u8> {

/// 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.
fn try_stretch<T>(mut in_vec: Vec<T>, target_len: usize) -> Option<Vec<T>>
where T: Default + Copy
// TODO(bech32): Default value parameter is added because `bech32::Fe32` does not have `Default`.
// If it get's added, the `default_value` parameter can be dropped (https://github.com/rust-bitcoin/rust-bech32/pull/184)
fn try_stretch<T>(mut in_vec: Vec<T>, target_len: usize, default_value: T) -> Option<Vec<T>>
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::<T>::with_capacity(target_len);
out_vec.append(&mut vec![T::default(); target_len - in_vec.len()]);
out_vec.append(&mut vec![default_value; target_len - in_vec.len()]);
out_vec.append(&mut in_vec);
Some(out_vec)
}
@@ -247,8 +326,8 @@ impl ToBase32 for PositiveTimestamp {
fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
// FIXME: use writer for int encoding
writer.write(
&try_stretch(encode_int_be_base32(self.as_unix_timestamp()), 7)
.expect("Can't be longer due than 7 u5s due to timestamp bounds")
&try_stretch(encode_int_be_base32(self.as_unix_timestamp()), 7, Fe32::Q)
.expect("Can't be longer due than 7 Fe32s due to timestamp bounds")
)
}
}
@@ -257,7 +336,7 @@ impl ToBase32 for RawTaggedField {
fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
match *self {
RawTaggedField::UnknownSemantics(ref content) => {
writer.write(content)
writer.write(&content.iter().map(|v| v.0).collect())
},
RawTaggedField::KnownSemantics(ref tagged_field) => {
tagged_field.write_base32(writer)
@@ -268,30 +347,30 @@ impl ToBase32 for RawTaggedField {

impl ToBase32 for Sha256 {
fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
(&self.0[..]).write_base32(writer)
(self.0[..].to_vec()).write_base32(writer)
}
}
impl Base32Len for Sha256 {
fn base32_len(&self) -> usize {
(&self.0[..]).base32_len()
(&self.0[..].to_vec()).base32_len()
}
}

impl ToBase32 for Description {
fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
self.0.0.as_bytes().write_base32(writer)
self.0.0.as_bytes().to_vec().write_base32(writer)
}
}

impl Base32Len for Description {
fn base32_len(&self) -> usize {
self.0.0.as_bytes().base32_len()
self.0.0.as_bytes().to_vec().base32_len()
}
}

impl ToBase32 for PayeePubKey {
fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
(&self.serialize()[..]).write_base32(writer)
(&self.serialize()[..]).to_vec().write_base32(writer)
}
}

@@ -329,16 +408,16 @@ impl ToBase32 for Fallback {
fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
match *self {
Fallback::SegWitProgram {version: v, program: ref p} => {
writer.write_u5(u5::try_from_u8(v.to_num()).expect("witness version <= 16"))?;
writer.write_fe32(Fe32::try_from(v.to_num()).expect("witness version <= 16"))?;
p.write_base32(writer)
},
Fallback::PubKeyHash(ref hash) => {
writer.write_u5(u5::try_from_u8(17).expect("17 < 32"))?;
(&hash[..]).write_base32(writer)
writer.write_fe32(Fe32::try_from(17).expect("17 < 32"))?;
(&hash[..]).to_vec().write_base32(writer)
},
Fallback::ScriptHash(ref hash) => {
writer.write_u5(u5::try_from_u8(18).expect("18 < 32"))?;
(&hash[..]).write_base32(writer)
writer.write_fe32(Fe32::try_from(18).expect("18 < 32"))?;
(&hash[..]).to_vec().write_base32(writer)
}
}
}
@@ -365,25 +444,25 @@ impl ToBase32 for PrivateRoute {
converter.append(&hop.src_node_id.serialize()[..])?;
let short_channel_id = try_stretch(
encode_int_be_base256(hop.short_channel_id),
8
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
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
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
2, u8::default()
).expect("sizeof(u16) == 2");
converter.append(&cltv_expiry_delta)?;
}
@@ -410,10 +489,10 @@ impl ToBase32 for TaggedField {
let len = payload.base32_len();
assert!(len < 1024, "Every tagged field data can be at most 1023 bytes long.");

writer.write_u5(u5::try_from_u8(tag).expect("invalid tag, not in 0..32"))?;
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
2, Fe32::Q
).expect("Can't be longer than 2, see assert above."))?;
payload.write_base32(writer)
}
@@ -468,8 +547,6 @@ impl ToBase32 for Bolt11InvoiceSignature {

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

#[test]
fn test_currency_code() {
use crate::Currency;
@@ -497,11 +574,12 @@ mod test {
#[test]
fn test_encode_int_be_base32() {
use crate::ser::encode_int_be_base32;
use bech32::Fe32;

let input: u64 = 33764;
let expected_out = CheckBase32::check_base32(&[1, 0, 31, 4]).unwrap();
let expected_out = &[1, 0, 31, 4].iter().copied().map(|v| Fe32::try_from(v).expect("should be <= 31")).collect::<Vec<Fe32>>();

assert_eq!(expected_out, encode_int_be_base32(input));
assert_eq!(expected_out.iter().copied().map(|v| v.to_char()).collect::<String>(), encode_int_be_base32(input).iter().copied().map(|v| v.to_char()).collect::<String>());
}

#[test]
8 changes: 4 additions & 4 deletions lightning-invoice/src/utils.rs
Original file line number Diff line number Diff line change
@@ -3,12 +3,12 @@
use crate::{Bolt11Invoice, CreationError, Currency, InvoiceBuilder, SignOrCreationError};

use crate::{prelude::*, Description, Bolt11InvoiceDescription, Sha256};
use bech32::ToBase32;
use crate::ToBase32;
use bitcoin::hashes::Hash;
use lightning::chain;
use lightning::chain::chaininterface::{BroadcasterInterface, FeeEstimator};
use lightning::sign::{Recipient, NodeSigner, SignerProvider, EntropySource};
use lightning::ln::types::{PaymentHash, PaymentSecret};
use lightning::ln::types::{InvoiceData, PaymentHash, PaymentSecret};
use lightning::ln::channel_state::ChannelDetails;
use lightning::ln::channelmanager::{ChannelManager, MIN_FINAL_CLTV_EXPIRY_DELTA};
use lightning::ln::channelmanager::{PhantomRouteHints, MIN_CLTV_EXPIRY_DELTA};
@@ -219,7 +219,7 @@ where
};
let hrp_str = raw_invoice.hrp.to_string();
let hrp_bytes = hrp_str.as_bytes();
let data_without_signature = raw_invoice.data.to_base32();
let data_without_signature = InvoiceData(raw_invoice.data.to_base32());
let signed_raw_invoice = raw_invoice.sign(|_| node_signer.sign_invoice(hrp_bytes, &data_without_signature, Recipient::PhantomNode));
match signed_raw_invoice {
Ok(inv) => Ok(Bolt11Invoice::from_signed(inv).unwrap()),
@@ -571,7 +571,7 @@ fn _create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_has
};
let hrp_str = raw_invoice.hrp.to_string();
let hrp_bytes = hrp_str.as_bytes();
let data_without_signature = raw_invoice.data.to_base32();
let data_without_signature = InvoiceData(raw_invoice.data.to_base32());
let signed_raw_invoice = raw_invoice.sign(|_| node_signer.sign_invoice(hrp_bytes, &data_without_signature, Recipient::Node));
match signed_raw_invoice {
Ok(inv) => Ok(Bolt11Invoice::from_signed(inv).unwrap()),
44 changes: 41 additions & 3 deletions lightning-invoice/tests/ser_de.rs
Original file line number Diff line number Diff line change
@@ -413,19 +413,21 @@ fn invoice_deserialize() {

#[test]
fn test_bolt_invalid_invoices() {
use bech32::primitives::decode::{CharError, ChecksumError, CheckedHrpstringError, UncheckedHrpstringError};

// Tests the BOLT 11 invalid invoice test vectors
assert_eq!(Bolt11Invoice::from_str(
"lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeessp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q4psqqqqqqqqqqqqqqqqsgqtqyx5vggfcsll4wu246hz02kp85x4katwsk9639we5n5yngc3yhqkm35jnjw4len8vrnqnf5ejh0mzj9n3vz2px97evektfm2l6wqccp3y7372"
), Err(ParseOrSemanticError::SemanticError(Bolt11SemanticError::InvalidFeatures)));
assert_eq!(Bolt11Invoice::from_str(
"lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpquwpc4curk03c9wlrswe78q4eyqc7d8d0xqzpuyk0sg5g70me25alkluzd2x62aysf2pyy8edtjeevuv4p2d5p76r4zkmneet7uvyakky2zr4cusd45tftc9c5fh0nnqpnl2jfll544esqchsrnt"
), Err(ParseOrSemanticError::ParseError(Bolt11ParseError::Bech32Error(bech32::Error::InvalidChecksum))));
), Err(ParseOrSemanticError::ParseError(Bolt11ParseError::Bech32Error(CheckedHrpstringError::Checksum(ChecksumError::InvalidResidue)))));
assert_eq!(Bolt11Invoice::from_str(
"pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpquwpc4curk03c9wlrswe78q4eyqc7d8d0xqzpuyk0sg5g70me25alkluzd2x62aysf2pyy8edtjeevuv4p2d5p76r4zkmneet7uvyakky2zr4cusd45tftc9c5fh0nnqpnl2jfll544esqchsrny"
), Err(ParseOrSemanticError::ParseError(Bolt11ParseError::Bech32Error(bech32::Error::MissingSeparator))));
), Err(ParseOrSemanticError::ParseError(Bolt11ParseError::Bech32Error(CheckedHrpstringError::Parse(UncheckedHrpstringError::Char(CharError::MissingSeparator))))));
assert_eq!(Bolt11Invoice::from_str(
"LNBC2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpquwpc4curk03c9wlrswe78q4eyqc7d8d0xqzpuyk0sg5g70me25alkluzd2x62aysf2pyy8edtjeevuv4p2d5p76r4zkmneet7uvyakky2zr4cusd45tftc9c5fh0nnqpnl2jfll544esqchsrny"
), Err(ParseOrSemanticError::ParseError(Bolt11ParseError::Bech32Error(bech32::Error::MixedCase))));
), Err(ParseOrSemanticError::ParseError(Bolt11ParseError::Bech32Error(CheckedHrpstringError::Parse(UncheckedHrpstringError::Char(CharError::MixedCase))))));
assert_eq!(Bolt11Invoice::from_str(
"lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpusp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9qrsgqwgt7mcn5yqw3yx0w94pswkpq6j9uh6xfqqqtsk4tnarugeektd4hg5975x9am52rz4qskukxdmjemg92vvqz8nvmsye63r5ykel43pgz7zq0g2"
), Err(ParseOrSemanticError::SemanticError(Bolt11SemanticError::InvalidSignature)));
@@ -439,3 +441,39 @@ fn test_bolt_invalid_invoices() {
"lnbc2500000001p1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpusp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9qrsgq0lzc236j96a95uv0m3umg28gclm5lqxtqqwk32uuk4k6673k6n5kfvx3d2h8s295fad45fdhmusm8sjudfhlf6dcsxmfvkeywmjdkxcp99202x"
), Err(ParseOrSemanticError::SemanticError(Bolt11SemanticError::ImpreciseAmount)));
}

#[test]
fn invoice_features_encoding() {
use crate::lightning_invoice::ToBase32;
use crate::lightning_invoice::FromBase32;
use lightning::ln::features::Bolt11InvoiceFeatures;
use bech32::{ByteIterExt, Fe32};

let features_as_fes = vec![
Fe32::try_from(6).unwrap(),
Fe32::try_from(10).unwrap(),
Fe32::try_from(25).unwrap(),
Fe32::try_from(1).unwrap(),
Fe32::try_from(10).unwrap(),
Fe32::try_from(0).unwrap(),
Fe32::try_from(20).unwrap(),
Fe32::try_from(2).unwrap(),
Fe32::try_from(0).unwrap(),
Fe32::try_from(6).unwrap(),
Fe32::try_from(0).unwrap(),
Fe32::try_from(16).unwrap(),
Fe32::try_from(1).unwrap(),
];
let features = Bolt11InvoiceFeatures::from_le_bytes(vec![1, 2, 3, 4, 5, 42, 100, 101]);

// Test length calculation.
assert_eq!(features.flags().iter().copied().bytes_to_fes().len(), 13);

// Test serialization.
let features_serialized = features.to_base32();
assert_eq!(features_as_fes, features_serialized);

// Test deserialization.
let features_deserialized = Bolt11InvoiceFeatures::from_base32(&features_as_fes).unwrap();
assert_eq!(features, features_deserialized);
}
2 changes: 1 addition & 1 deletion lightning/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 }
98 changes: 3 additions & 95 deletions lightning/src/ln/features.rs
Original file line number Diff line number Diff line change
@@ -87,7 +87,6 @@ use core::borrow::Borrow;
use core::hash::{Hash, Hasher};
use core::marker::PhantomData;

use bech32::{Base32Len, FromBase32, ToBase32, u5, WriteBase32};
use crate::ln::msgs::DecodeError;
use crate::util::ser::{Readable, WithoutLength, Writeable, Writer};

@@ -581,6 +580,9 @@ impl Bolt11InvoiceFeatures {
self.to_context_internal()
}

/// Accessor for flags
pub fn flags(&self) -> &Vec<u8> { &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 in
@@ -640,68 +642,6 @@ impl ChannelTypeFeatures {
}
}

impl ToBase32 for Bolt11InvoiceFeatures {
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 Bolt11InvoiceFeatures {
fn base32_len(&self) -> usize {
self.to_base32().len()
}
}

impl FromBase32 for Bolt11InvoiceFeatures {
type Err = bech32::Error;

fn from_base32(field_data: &[u5]) -> Result<Bolt11InvoiceFeatures, 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(Bolt11InvoiceFeatures::from_le_bytes(res_bytes))
}
}

impl<T: sealed::Context> Features<T> {
/// Create a blank Features with no features set
pub fn empty() -> Self {
@@ -1043,7 +983,6 @@ pub(crate) fn unset_features_mask_at_position<T: sealed::Context>(other: &Featur
#[cfg(test)]
mod tests {
use super::{ChannelFeatures, ChannelTypeFeatures, InitFeatures, Bolt11InvoiceFeatures, NodeFeatures, OfferFeatures, sealed};
use bech32::{Base32Len, FromBase32, ToBase32, u5};
use crate::util::ser::{Readable, WithoutLength, Writeable};

#[test]
@@ -1241,37 +1180,6 @@ mod tests {
assert_eq!(features, deserialized_features);
}

#[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 = Bolt11InvoiceFeatures::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 = Bolt11InvoiceFeatures::from_base32(&features_as_u5s).unwrap();
assert_eq!(features, features_deserialized);
}

#[test]
fn test_channel_type_mapping() {
// If we map an Bolt11InvoiceFeatures with StaticRemoteKey optional, it should map into a
30 changes: 3 additions & 27 deletions lightning/src/ln/types.rs
Original file line number Diff line number Diff line change
@@ -162,34 +162,10 @@ impl From<PaymentPreimage> for PaymentHash {
#[derive(Hash, Copy, Clone, PartialEq, Eq, Debug, Ord, PartialOrd)]
pub struct PaymentSecret(pub [u8; 32]);

use bech32::{Base32Len, FromBase32, ToBase32, WriteBase32, u5};
use bech32::Fe32;

impl FromBase32 for PaymentSecret {
type Err = bech32::Error;

fn from_base32(field_data: &[u5]) -> Result<PaymentSecret, bech32::Error> {
if field_data.len() != 52 {
return Err(bech32::Error::InvalidLength)
} 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))
}
}
}

impl ToBase32 for PaymentSecret {
fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
(&self.0[..]).write_base32(writer)
}
}

impl Base32Len for PaymentSecret {
fn base32_len(&self) -> usize {
52
}
}
/// Invoice data
pub struct InvoiceData(pub Vec<Fe32>);

#[cfg(test)]
mod tests {
30 changes: 17 additions & 13 deletions lightning/src/offers/parse.rs
Original file line number Diff line number Diff line change
@@ -13,6 +13,7 @@ use bitcoin::secp256k1;
use crate::io;
use crate::ln::msgs::DecodeError;
use crate::util::ser::SeekReadable;
use bech32::primitives::decode::CheckedHrpstringError;

#[allow(unused_imports)]
use crate::prelude::*;
@@ -24,7 +25,8 @@ pub(super) use sealed::Bech32Encode;
pub use sealed::Bech32Encode;

mod sealed {
use bech32::{FromBase32, ToBase32};
use bech32::{EncodeError, Hrp, NoChecksum, encode_to_fmt};
use bech32::primitives::decode::CheckedHrpstring;
use core::fmt;
use super::Bolt12ParseError;

@@ -54,22 +56,23 @@ mod sealed {
None => Bech32String::Borrowed(s),
};

let (hrp, data) = bech32::decode_without_checksum(encoded.as_ref())?;

if hrp != Self::BECH32_HRP {
let parsed = CheckedHrpstring::new::<NoChecksum>(encoded.as_ref())?;
let hrp = parsed.hrp();
if hrp.to_string() != Self::BECH32_HRP {
return Err(Bolt12ParseError::InvalidBech32Hrp);
}

let data = Vec::<u8>::from_base32(&data)?;
let data = parsed.byte_iter().collect::<Vec<u8>>();
Self::try_from(data)
}

/// Formats the message using bech32-encoding.
fn fmt_bech32_str(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
bech32::encode_without_checksum_to_fmt(f, Self::BECH32_HRP, self.as_ref().to_base32())
.expect("HRP is invalid").unwrap();

Ok(())
encode_to_fmt::<NoChecksum, _>(f, Hrp::parse(Self::BECH32_HRP).unwrap(), self.as_ref())
.map_err(|e| match e {
EncodeError::Fmt(e) => e,
_ => fmt::Error::default(),
})
}
}

@@ -123,7 +126,7 @@ pub enum Bolt12ParseError {
/// being parsed.
InvalidBech32Hrp,
/// The string could not be bech32 decoded.
Bech32(bech32::Error),
Bech32(CheckedHrpstringError),
/// The bech32 decoded string could not be decoded as the expected message type.
Decode(DecodeError),
/// The parsed message has invalid semantics.
@@ -195,8 +198,8 @@ pub enum Bolt12SemanticError {
MissingSignature,
}

impl From<bech32::Error> for Bolt12ParseError {
fn from(error: bech32::Error) -> Self {
impl From<CheckedHrpstringError> for Bolt12ParseError {
fn from(error: CheckedHrpstringError) -> Self {
Self::Bech32(error)
}
}
@@ -279,6 +282,7 @@ mod tests {
use super::Bolt12ParseError;
use crate::ln::msgs::DecodeError;
use crate::offers::offer::Offer;
use bech32::primitives::decode::{CharError, CheckedHrpstringError, UncheckedHrpstringError};

#[test]
fn fails_parsing_bech32_encoded_offer_with_invalid_hrp() {
@@ -294,7 +298,7 @@ mod tests {
let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxo";
match encoded_offer.parse::<Offer>() {
Ok(_) => panic!("Valid offer: {}", encoded_offer),
Err(e) => assert_eq!(e, Bolt12ParseError::Bech32(bech32::Error::InvalidChar('o'))),
Err(e) => assert_eq!(e, Bolt12ParseError::Bech32(CheckedHrpstringError::Parse(UncheckedHrpstringError::Char(CharError::InvalidChar('o'))))),
}
}

9 changes: 4 additions & 5 deletions lightning/src/sign/mod.rs
Original file line number Diff line number Diff line change
@@ -24,7 +24,6 @@ use bitcoin::sighash;
use bitcoin::sighash::EcdsaSighashType;
use bitcoin::transaction::Version;

use bech32::u5;
use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::hashes::sha256d::Hash as Sha256dHash;
use bitcoin::hashes::{Hash, HashEngine};
@@ -54,7 +53,7 @@ use crate::ln::channel_keys::{
use crate::ln::msgs::PartialSignatureWithNonce;
use crate::ln::msgs::{UnsignedChannelAnnouncement, UnsignedGossipMessage};
use crate::ln::script::ShutdownScript;
use crate::ln::types::PaymentPreimage;
use crate::ln::types::{InvoiceData, PaymentPreimage};
use crate::offers::invoice::UnsignedBolt12Invoice;
use crate::offers::invoice_request::UnsignedInvoiceRequest;
use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer};
@@ -856,7 +855,7 @@ pub trait NodeSigner {
///
/// Errors if the [`Recipient`] variant is not supported by the implementation.
fn sign_invoice(
&self, hrp_bytes: &[u8], invoice_data: &[u5], recipient: Recipient,
&self, hrp_bytes: &[u8], invoice_data: &InvoiceData, recipient: Recipient,
) -> Result<RecoverableSignature, ()>;

/// Signs the [`TaggedHash`] of a BOLT 12 invoice request.
@@ -2163,7 +2162,7 @@ impl NodeSigner for KeysManager {
}

fn sign_invoice(
&self, hrp_bytes: &[u8], invoice_data: &[u5], recipient: Recipient,
&self, hrp_bytes: &[u8], invoice_data: &InvoiceData, recipient: Recipient,
) -> Result<RecoverableSignature, ()> {
let preimage = construct_invoice_preimage(&hrp_bytes, &invoice_data);
let secret = match recipient {
@@ -2341,7 +2340,7 @@ impl NodeSigner for PhantomKeysManager {
}

fn sign_invoice(
&self, hrp_bytes: &[u8], invoice_data: &[u5], recipient: Recipient,
&self, hrp_bytes: &[u8], invoice_data: &InvoiceData, recipient: Recipient,
) -> Result<RecoverableSignature, ()> {
let preimage = construct_invoice_preimage(&hrp_bytes, &invoice_data);
let secret = match recipient {
16 changes: 9 additions & 7 deletions lightning/src/util/invoice.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,30 @@
//! Low level invoice utilities.
use bech32::{u5, FromBase32};
use crate::ln::types::InvoiceData;
use bech32::{Fe32, Fe32IterExt};

#[allow(unused)]
use crate::prelude::*;

/// Construct the invoice's HRP and signatureless data into a preimage to be hashed.
pub fn construct_invoice_preimage(hrp_bytes: &[u8], data_without_signature: &[u5]) -> Vec<u8> {
/// TODO(bech32): This should be moved to lightning-invoice crate, and use FromBase32 from there
pub fn construct_invoice_preimage(hrp_bytes: &[u8], data_without_signature: &InvoiceData) -> Vec<u8> {
let mut preimage = Vec::<u8>::from(hrp_bytes);

let mut data_part = Vec::from(data_without_signature);
let mut data_part = data_without_signature.0.clone();
let overhang = (data_part.len() * 5) % 8;
if overhang > 0 {
// add padding if data does not end at a byte boundary
data_part.push(u5::try_from_u8(0).unwrap());
data_part.push(Fe32::try_from(0).unwrap());

// if overhang is in (1..3) we need to add u5(0) padding two times
if overhang < 3 {
data_part.push(u5::try_from_u8(0).unwrap());
data_part.push(Fe32::try_from(0).unwrap());
}
}

preimage.extend_from_slice(&Vec::<u8>::from_base32(&data_part)
.expect("No padding error may occur due to appended zero above."));
// TODO(bech32): Should use FromBase32 from lightning-invoice crate
preimage.extend(&data_part.iter().copied().fes_to_bytes().collect::<Vec<u8>>());
preimage
}

6 changes: 3 additions & 3 deletions lightning/src/util/test_utils.rs
Original file line number Diff line number Diff line change
@@ -34,6 +34,7 @@ use crate::ln::features::{ChannelFeatures, InitFeatures, NodeFeatures};
use crate::ln::{msgs, wire};
use crate::ln::msgs::LightningError;
use crate::ln::script::ShutdownScript;
use crate::ln::types::InvoiceData;
use crate::offers::invoice::{BlindedPayInfo, UnsignedBolt12Invoice};
use crate::offers::invoice_request::UnsignedInvoiceRequest;
use crate::onion_message::messenger::{DefaultMessageRouter, Destination, MessageRouter, OnionMessagePath};
@@ -72,7 +73,6 @@ use core::time::Duration;
use crate::sync::{Mutex, Arc};
use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use core::mem;
use bech32::u5;
use crate::sign::{InMemorySigner, RandomBytes, Recipient, EntropySource, NodeSigner, SignerProvider};

#[cfg(feature = "std")]
@@ -1217,7 +1217,7 @@ impl NodeSigner for TestNodeSigner {
Ok(SharedSecret::new(other_key, &node_secret))
}

fn sign_invoice(&self, _: &[u8], _: &[bech32::u5], _: Recipient) -> Result<bitcoin::secp256k1::ecdsa::RecoverableSignature, ()> {
fn sign_invoice(&self, _: &[u8], _: &InvoiceData, _: Recipient) -> Result<bitcoin::secp256k1::ecdsa::RecoverableSignature, ()> {
unreachable!()
}

@@ -1270,7 +1270,7 @@ impl NodeSigner for TestKeysInterface {
self.backing.get_inbound_payment_key_material()
}

fn sign_invoice(&self, hrp_bytes: &[u8], invoice_data: &[u5], recipient: Recipient) -> Result<RecoverableSignature, ()> {
fn sign_invoice(&self, hrp_bytes: &[u8], invoice_data: &InvoiceData, recipient: Recipient) -> Result<RecoverableSignature, ()> {
self.backing.sign_invoice(hrp_bytes, invoice_data, recipient)
}