diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index 3615850a22e..e1f5c13daf6 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -138,7 +138,7 @@ use crate::offers::offer::{ Amount, ExperimentalOfferTlvStream, ExperimentalOfferTlvStreamRef, OfferTlvStream, OfferTlvStreamRef, Quantity, EXPERIMENTAL_OFFER_TYPES, OFFER_TYPES, }; -use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError, ParsedMessage}; +use crate::offers::parse::{Bech32Encode, Bolt12ParseError, Bolt12SemanticError, ParsedMessage}; use crate::offers::payer::{PayerTlvStream, PayerTlvStreamRef, PAYER_METADATA_TYPE}; use crate::offers::refund::{ Refund, RefundContents, IV_BYTES_WITHOUT_METADATA as REFUND_IV_BYTES_WITHOUT_METADATA, @@ -158,6 +158,7 @@ use bitcoin::secp256k1::schnorr::Signature; use bitcoin::secp256k1::{self, Keypair, PublicKey, Secp256k1}; use bitcoin::{Network, WitnessProgram, WitnessVersion}; use core::hash::{Hash, Hasher}; +use core::str::FromStr; use core::time::Duration; #[allow(unused_imports)] @@ -1416,6 +1417,30 @@ impl Writeable for InvoiceContents { } } +impl AsRef<[u8]> for Bolt12Invoice { + fn as_ref(&self) -> &[u8] { + &self.bytes + } +} + +impl Bech32Encode for Bolt12Invoice { + const BECH32_HRP: &'static str = "lni"; +} + +impl FromStr for Bolt12Invoice { + type Err = Bolt12ParseError; + + fn from_str(s: &str) -> Result::Err> { + Self::from_bech32_str(s) + } +} + +impl core::fmt::Display for Bolt12Invoice { + fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> { + self.fmt_bech32_str(f) + } +} + impl TryFrom> for UnsignedBolt12Invoice { type Error = Bolt12ParseError; @@ -2572,6 +2597,22 @@ mod tests { } } + #[test] + fn parses_bech32_encoded_invoices() { + let invoices = [ + "lni1qqsg7jpsyzz4hcsj0hu6rvjevwhmkceurq7sd5ez8ne3js4qt8acvxcgqgp7szsqzcss9w6ckhlv55zuwnkuqqxc9qhu24h9rggzflyw04l9d3hcslzu340jtqss9l7txvy6ukzg8zkxdnvzmg2at4stt004vdqrm0zedsez596nf5w55r7sr3qzhfe2d696205tjuddpjvz8952aaxh3n527f26ks7llqcq8jgzlwxsxhzwphk8y90zdqee8pesuhjst2nz2px6ska9wyr2g666ysz0e8vwqgptkk94lm99qhr5ahqqpkpg9lz4deg6zqj0erna0etvd7y8chydtusq9vqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqz3afsfc3h8etwulthfjufa8c6lm8saelrud6h7xyeprcxnk4rd3sqqtqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq46w2nw3wjnazuhrtgvnq3edzh0f4uvazhj2k458hlcxqpujqhm35p4cnsda3eptcngxwfcwv89u5z65cjsfk59hft3q6jxkk3yqn7fmrszqw2gk576jl7lvaxqsae3tt9uepmp4gae5kptgwvc97a04jvljuss7qpdqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq0vc5l00vl5rwqgc7cmxyrgtuz8dvv6yma5qs2609uvyfe7wvq2gxqpwqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqz3rsqqqqqqsqqqraqqz5qqqqqqqqqqqvsqqqq8g6jj3qqqqqqqqqqqpqqqq86qq9gqqqqqqqqqqqeqqqqqw3499zqqqqq9yq35rr8sh4qsz52329g4z52329g4z52329g4z52329g4z52329g4z52329g4z5242qgp73tzaqqqzpcasc3pf3lquzjd0haxgn9hmjfp84eq7geymjdx2f9verdu99wz4qqqpf67qrgen88wz7kzlkpyp480l5rgzecaz2qgqyza43d07efg9ca8dcqqds2p0c4tw2xssyn7gult72mr03p79er2l9vppq2a43d07efg9ca8dcqqds2p0c4tw2xssyn7gult72mr03p79er2l9uzq9wktr4p2qxgmdnpw8qvs05qr0zvam2h52lxt4zz7lah7yp6vmsczevlvqdgjxtwdlp84304uqcygvqcgzpj8p44smqjpzeua0xryrrc" + ]; + for encoded in invoices { + let decoded = match encoded.parse::() { + Ok(decoded) => decoded, + Err(e) => panic!("Invalid invoice ({:?}): {}", e, encoded), + }; + + let reencoded = decoded.to_string(); + assert_eq!(reencoded, encoded, "Re-encoded invoice does not match original"); + } + } + #[test] fn parses_invoice_with_payment_paths() { let expanded_key = ExpandedKey::new([42; 32]); diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index 2e399738bae..77848d97fe2 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -65,6 +65,8 @@ //! # } //! ``` +use core::str::FromStr; + use crate::blinded_path::message::BlindedMessagePath; use crate::blinded_path::payment::BlindedPaymentPath; use crate::io; @@ -79,7 +81,7 @@ use crate::offers::offer::{ Amount, ExperimentalOfferTlvStream, ExperimentalOfferTlvStreamRef, Offer, OfferContents, OfferId, OfferTlvStream, OfferTlvStreamRef, EXPERIMENTAL_OFFER_TYPES, OFFER_TYPES, }; -use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError, ParsedMessage}; +use crate::offers::parse::{Bech32Encode, Bolt12ParseError, Bolt12SemanticError, ParsedMessage}; use crate::offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef}; use crate::offers::signer::{Metadata, MetadataMaterial}; use crate::onion_message::dns_resolution::HumanReadableName; @@ -1284,6 +1286,30 @@ impl TryFrom> for UnsignedInvoiceRequest { } } +impl AsRef<[u8]> for InvoiceRequest { + fn as_ref(&self) -> &[u8] { + &self.bytes + } +} + +impl Bech32Encode for InvoiceRequest { + const BECH32_HRP: &'static str = "lnr"; +} + +impl FromStr for InvoiceRequest { + type Err = Bolt12ParseError; + + fn from_str(s: &str) -> Result::Err> { + Self::from_bech32_str(s) + } +} + +impl core::fmt::Display for InvoiceRequest { + fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> { + self.fmt_bech32_str(f) + } +} + impl TryFrom> for InvoiceRequest { type Error = Bolt12ParseError; @@ -2219,6 +2245,22 @@ mod tests { } } + #[test] + fn parses_bech32_encoded_invoice_requests() { + let invoice_requests = [ + "lnr1qqsg7jpsyzz4hcsj0hu6rvjevwhmkceurq7sd5ez8ne3js4qt8acvxcgqgp7szsqzsqpvggzhdvttlk22pw8fmwqqrvzst792mj35ypylj886ljkcmug03wg6he9yqs86ptqzqjcyypqk4jf95qryjcsqywr6kktzrf366ex4yp8cr5r8m32cre3kfea7w0sgzegrzqgucwd37cjyvkgg2lfae8j6wyyx7dj3aqe8j2ncrthhszl8r69lecma5cxclmft4kh8x39jaeqtdl2yy5gsfdqcpvxczf5x0sw" + ]; + for encoded in invoice_requests { + let decoded = match encoded.parse::() { + Ok(decoded) => decoded, + Err(e) => panic!("Invalid invoice request ({:?}): {}", e, encoded), + }; + + let reencoded = decoded.to_string(); + assert_eq!(reencoded, encoded, "Re-encoded invoice does not match original"); + } + } + #[test] fn parses_invoice_request_with_metadata() { let expanded_key = ExpandedKey::new([42; 32]); diff --git a/lightning/src/offers/merkle.rs b/lightning/src/offers/merkle.rs index 3c84e7a7a17..67029495bab 100644 --- a/lightning/src/offers/merkle.rs +++ b/lightning/src/offers/merkle.rs @@ -285,10 +285,9 @@ mod tests { use crate::ln::channelmanager::PaymentId; use crate::ln::inbound_payment::ExpandedKey; - use crate::offers::invoice_request::{InvoiceRequest, UnsignedInvoiceRequest}; + use crate::offers::invoice_request::UnsignedInvoiceRequest; use crate::offers::nonce::Nonce; use crate::offers::offer::{Amount, OfferBuilder}; - use crate::offers::parse::Bech32Encode; use crate::offers::signer::Metadata; use crate::offers::test_utils::recipient_pubkey; use crate::util::ser::Writeable; @@ -477,20 +476,4 @@ mod tests { assert_eq!(tlv_stream, invoice_request.bytes); } - - impl AsRef<[u8]> for InvoiceRequest { - fn as_ref(&self) -> &[u8] { - &self.bytes - } - } - - impl Bech32Encode for InvoiceRequest { - const BECH32_HRP: &'static str = "lnr"; - } - - impl core::fmt::Display for InvoiceRequest { - fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> { - self.fmt_bech32_str(f) - } - } }