Skip to content

Commit 6555fae

Browse files
committed
f - move bech32 parsing
1 parent c5af9b5 commit 6555fae

File tree

4 files changed

+128
-93
lines changed

4 files changed

+128
-93
lines changed

lightning/src/offers/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,10 @@
1212
1313
mod merkle;
1414
mod offer;
15+
mod parse;
1516

1617
pub use self::offer::{Amount, BlindedPath, CurrencyCode, Destination, Offer, OfferBuilder};
18+
19+
tlv_stream!(struct PayerTlvStream {
20+
(0, payer_info: Vec<u8>),
21+
});

lightning/src/offers/offer.rs

Lines changed: 16 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99

1010
//! Data structures and encoding for `offer` messages.
1111
12-
use bitcoin::bech32;
13-
use bitcoin::bech32::FromBase32;
1412
use bitcoin::blockdata::constants::genesis_block;
1513
use bitcoin::hash_types::BlockHash;
1614
use bitcoin::network::constants::Network;
@@ -22,8 +20,8 @@ use core::str::FromStr;
2220
use core::time::Duration;
2321
use io;
2422
use ln::features::OfferFeatures;
25-
use ln::msgs::DecodeError;
26-
use util::ser::{Readable, WithLength, Writeable, Writer};
23+
use offers::parse::{Bech32Encode, ParseError, SemanticError};
24+
use util::ser::{WithLength, Writeable, Writer};
2725

2826
use prelude::*;
2927

@@ -275,6 +273,7 @@ impl Offer {
275273
self.contents.send_invoice.as_ref()
276274
}
277275

276+
#[cfg(test)]
278277
fn as_bytes(&self) -> &[u8] {
279278
&self.bytes
280279
}
@@ -292,6 +291,12 @@ impl Offer {
292291
}
293292
}
294293

294+
impl AsRef<[u8]> for Offer {
295+
fn as_ref(&self) -> &[u8] {
296+
&self.bytes
297+
}
298+
}
299+
295300
impl OfferContents {
296301
pub fn quantity_min(&self) -> u64 {
297302
self.quantity_min.unwrap_or(1)
@@ -394,67 +399,17 @@ impl_writeable!(OnionMessagePath, { node_id, encrypted_recipient_data });
394399

395400
type Empty = ();
396401

397-
/// An `offer` parsed from a bech32-encoded string as a TLV stream and the corresponding bytes. The
398-
/// latter is used to reflect fields in an `invoice_request`, some of which may be unknown.
399-
struct ParsedOffer(OfferTlvStream, Vec<u8>);
400-
401-
/// Error when parsing a bech32 encoded message using [`str::parse`].
402-
#[derive(Debug, PartialEq)]
403-
pub enum ParseError {
404-
/// The bech32 encoding does not conform to the BOLT 12 requirements for continuing messages
405-
/// across multiple parts (i.e., '+' followed by whitespace).
406-
InvalidContinuation,
407-
/// The bech32 encoding's human-readable part does not match what was expected for the message
408-
/// being parsed.
409-
InvalidBech32Hrp,
410-
/// The string could not be bech32 decoded.
411-
Bech32(bech32::Error),
412-
/// The bech32 decoded string could not be decoded as the expected message type.
413-
Decode(DecodeError),
414-
/// The parsed message has invalid semantics.
415-
InvalidSemantics(SemanticError),
416-
}
417-
418-
/// Error when interpreting a TLV stream as a specific type.
419-
#[derive(Debug, PartialEq)]
420-
pub enum SemanticError {
421-
/// The provided block hash does not correspond to a supported chain.
422-
UnsupportedChain,
423-
/// A currency was provided without an amount.
424-
UnexpectedCurrency,
425-
/// A required description was not provided.
426-
MissingDescription,
427-
/// Either a node id or a blinded path was not provided.
428-
MissingDestination,
429-
/// An empty set of blinded paths was provided.
430-
MissingPaths,
431-
/// A quantity representing an empty range or that was outside of a valid range was provided.
432-
InvalidQuantity,
433-
}
402+
impl Bech32Encode for Offer {
403+
type TlvStream = OfferTlvStream;
434404

435-
impl From<bech32::Error> for ParseError {
436-
fn from(error: bech32::Error) -> Self {
437-
Self::Bech32(error)
438-
}
439-
}
440-
441-
impl From<DecodeError> for ParseError {
442-
fn from(error: DecodeError) -> Self {
443-
Self::Decode(error)
444-
}
445-
}
446-
447-
impl From<SemanticError> for ParseError {
448-
fn from(error: SemanticError) -> Self {
449-
Self::InvalidSemantics(error)
450-
}
405+
const BECH32_HRP: &'static str = "lno";
451406
}
452407

453408
impl FromStr for Offer {
454409
type Err = ParseError;
455410

456411
fn from_str(s: &str) -> Result<Self, <Self as FromStr>::Err> {
457-
let ParsedOffer(tlv_stream, bytes) = ParsedOffer::from_str(s)?;
412+
let (tlv_stream, bytes) = Offer::from_bech32_str(s)?;
458413
let contents = OfferContents::try_from(tlv_stream)?;
459414
Ok(Offer { bytes, contents })
460415
}
@@ -539,39 +494,9 @@ impl TryFrom<OfferTlvStream> for OfferContents {
539494
}
540495
}
541496

542-
const OFFER_BECH32_HRP: &str = "lno";
543-
544-
impl FromStr for ParsedOffer {
545-
type Err = ParseError;
546-
547-
fn from_str(s: &str) -> Result<Self, <Self as FromStr>::Err> {
548-
// Offer encoding may be split by '+' followed by optional whitespace.
549-
for chunk in s.split('+') {
550-
let chunk = chunk.trim_start();
551-
if chunk.is_empty() || chunk.contains(char::is_whitespace) {
552-
return Err(ParseError::InvalidContinuation);
553-
}
554-
}
555-
556-
let s = s.chars().filter(|c| *c != '+' && !c.is_whitespace()).collect::<String>();
557-
let (hrp, data) = bech32::decode_without_checksum(&s)?;
558-
559-
if hrp != OFFER_BECH32_HRP {
560-
return Err(ParseError::InvalidBech32Hrp);
561-
}
562-
563-
let data = Vec::<u8>::from_base32(&data)?;
564-
Ok(ParsedOffer(Readable::read(&mut &data[..])?, data))
565-
}
566-
}
567-
568497
impl core::fmt::Display for Offer {
569498
fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
570-
use bitcoin::bech32::ToBase32;
571-
let data = self.as_bytes().to_base32();
572-
bech32::encode_without_checksum_to_fmt(f, OFFER_BECH32_HRP, data).expect("HRP is valid").unwrap();
573-
574-
Ok(())
499+
self.fmt_bech32_str(f)
575500
}
576501
}
577502

@@ -955,7 +880,7 @@ mod tests {
955880

956881
#[cfg(test)]
957882
mod bolt12_tests {
958-
use super::{Offer, ParseError, ParsedOffer};
883+
use super::{Offer, ParseError};
959884
use bitcoin::bech32;
960885
use ln::msgs::DecodeError;
961886

@@ -981,8 +906,7 @@ mod bolt12_tests {
981906
"lno1qcp4256ypqpq86q2pucnq42ngssx2an9wfujqerp0yg06qg2qdd7t628sgykwj5kuc837qmlv9m9gr7sq8ap6erfgacv26nhp8zzcqgzhdvttlk22pw8fmwqqrvzst792mj35ypylj886ljkcmug03wg6heqqsqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq6muh550qsfva9fdes0ruph7ctk2s8aqq06r4jxj3msc448wzwy9sqs9w6ckhlv55zuwnkuqqxc9qhu24h9rggzflyw04l9d3hcslzu340jqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq2pqun4wd68jtn00fkxzcnn9ehhyec6qgqsz83qfwdpl28qqmc78ymlvhmxcsywdk5wrjnj36jryg488qwlrnzyjczlqsp9nyu4phcg6dqhlhzgxagfu7zh3d9re0sqp9ts2yfugvnnm9gxkcnnnkdpa084a6t520h5zhkxsdnghvpukvd43lastpwuh73k29qsy",
982907
];
983908
for encoded_offer in &offers {
984-
// TODO: Use Offer once Destination semantics are finalized.
985-
if let Err(e) = encoded_offer.parse::<ParsedOffer>() {
909+
if let Err(e) = encoded_offer.parse::<Offer>() {
986910
panic!("Invalid offer ({:?}): {}", e, encoded_offer);
987911
}
988912
}

lightning/src/offers/parse.rs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// This file is Copyright its original authors, visible in version control
2+
// history.
3+
//
4+
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
5+
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
7+
// You may not use this file except in accordance with one or both of these
8+
// licenses.
9+
10+
//! Parsing and formatting for bech32 message encoding.
11+
12+
use bitcoin::bech32;
13+
use bitcoin::bech32::{FromBase32, ToBase32};
14+
use core::fmt;
15+
use ln::msgs::DecodeError;
16+
use util::ser::Readable;
17+
18+
/// Indicates a message can be encoded using bech32.
19+
pub(crate) trait Bech32Encode: AsRef<[u8]> {
20+
/// TLV stream that a bech32-encoded message is parsed into.
21+
type TlvStream: Readable;
22+
23+
/// Human readable part of the message's bech32 encoding.
24+
const BECH32_HRP: &'static str;
25+
26+
/// Parses a bech32-encoded message into a TLV stream.
27+
fn from_bech32_str(s: &str) -> Result<(Self::TlvStream, Vec<u8>), ParseError> {
28+
// Offer encoding may be split by '+' followed by optional whitespace.
29+
for chunk in s.split('+') {
30+
let chunk = chunk.trim_start();
31+
if chunk.is_empty() || chunk.contains(char::is_whitespace) {
32+
return Err(ParseError::InvalidContinuation);
33+
}
34+
}
35+
36+
let s = s.chars().filter(|c| *c != '+' && !c.is_whitespace()).collect::<String>();
37+
let (hrp, data) = bech32::decode_without_checksum(&s)?;
38+
39+
if hrp != Self::BECH32_HRP {
40+
return Err(ParseError::InvalidBech32Hrp);
41+
}
42+
43+
let data = Vec::<u8>::from_base32(&data)?;
44+
Ok((Readable::read(&mut &data[..])?, data))
45+
}
46+
47+
/// Formats the message using bech32-encoding.
48+
fn fmt_bech32_str(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
49+
bech32::encode_without_checksum_to_fmt(f, Self::BECH32_HRP, self.as_ref().to_base32())
50+
.expect("HRP is valid").unwrap();
51+
52+
Ok(())
53+
}
54+
}
55+
56+
/// Error when parsing a bech32 encoded message using [`str::parse`].
57+
#[derive(Debug, PartialEq)]
58+
pub enum ParseError {
59+
/// The bech32 encoding does not conform to the BOLT 12 requirements for continuing messages
60+
/// across multiple parts (i.e., '+' followed by whitespace).
61+
InvalidContinuation,
62+
/// The bech32 encoding's human-readable part does not match what was expected for the message
63+
/// being parsed.
64+
InvalidBech32Hrp,
65+
/// The string could not be bech32 decoded.
66+
Bech32(bech32::Error),
67+
/// The bech32 decoded string could not be decoded as the expected message type.
68+
Decode(DecodeError),
69+
/// The parsed message has invalid semantics.
70+
InvalidSemantics(SemanticError),
71+
}
72+
73+
/// Error when interpreting a TLV stream as a specific type.
74+
#[derive(Debug, PartialEq)]
75+
pub enum SemanticError {
76+
/// The provided block hash does not correspond to a supported chain.
77+
UnsupportedChain,
78+
/// A currency was provided without an amount.
79+
UnexpectedCurrency,
80+
/// A required description was not provided.
81+
MissingDescription,
82+
/// Either a node id or a blinded path was not provided.
83+
MissingDestination,
84+
/// An empty set of blinded paths was provided.
85+
MissingPaths,
86+
/// A quantity representing an empty range or that was outside of a valid range was provided.
87+
InvalidQuantity,
88+
}
89+
90+
impl From<bech32::Error> for ParseError {
91+
fn from(error: bech32::Error) -> Self {
92+
Self::Bech32(error)
93+
}
94+
}
95+
96+
impl From<DecodeError> for ParseError {
97+
fn from(error: DecodeError) -> Self {
98+
Self::Decode(error)
99+
}
100+
}
101+
102+
impl From<SemanticError> for ParseError {
103+
fn from(error: SemanticError) -> Self {
104+
Self::InvalidSemantics(error)
105+
}
106+
}

lightning/src/util/ser_macros.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,7 @@ macro_rules! tlv_stream {
435435
$(($type:expr, $field:ident : $fieldty:ident$(<$gen:ident>)?)),* $(,)*
436436
}) => {
437437
#[derive(Debug)]
438-
struct $name {
438+
pub(crate) struct $name {
439439
$(
440440
$field: Option<tlv_record_type!($fieldty$(<$gen>)?)>,
441441
)*

0 commit comments

Comments
 (0)