From 8e279f719dcc1677714a26c56140193bd70cfe58 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Mon, 12 Aug 2024 16:54:55 -0500 Subject: [PATCH] Parse experimental invoice TLV records The BOLT12 spec defines an experimental TLV range that is allowed in offer and invoice_request messages. The remaining TLV-space is for experimental use in invoice messages. Allow this range when parsing an invoice and include it when signing one. --- lightning/src/offers/invoice.rs | 83 ++++++++++++++++--------- lightning/src/offers/invoice_request.rs | 12 ++-- lightning/src/offers/static_invoice.rs | 61 +++++++++++++----- lightning/src/util/ser.rs | 24 +++++++ 4 files changed, 134 insertions(+), 46 deletions(-) diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index eb2a0cdcc..08c4c6a78 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -497,7 +497,8 @@ impl UnsignedBolt12Invoice { const EXPERIMENTAL_TYPES: core::ops::Range = EXPERIMENTAL_OFFER_TYPES.start..EXPERIMENTAL_INVOICE_REQUEST_TYPES.end; - let (_, _, _, invoice_tlv_stream, _, _) = contents.as_tlv_stream(); + let (_, _, _, invoice_tlv_stream, _, _, experimental_invoice_tlv_stream) = + contents.as_tlv_stream(); // Allocate enough space for the invoice, which will include: // - all TLV records from `invreq_bytes` except signatures, @@ -510,6 +511,7 @@ impl UnsignedBolt12Invoice { invreq_bytes.len() + invoice_tlv_stream.serialized_length() + if contents.is_for_offer() { 0 } else { SIGNATURE_TLV_RECORD_SIZE } + + experimental_invoice_tlv_stream.serialized_length(), ); // Use the invoice_request bytes instead of the invoice_request TLV stream as the latter may @@ -531,12 +533,14 @@ impl UnsignedBolt12Invoice { - experimental_tlv_stream .peek() .map_or(remaining_bytes.len(), |first_record| first_record.start) + + experimental_invoice_tlv_stream.serialized_length(), ); for record in experimental_tlv_stream { record.write(&mut experimental_bytes).unwrap(); } + experimental_invoice_tlv_stream.write(&mut experimental_bytes).unwrap(); debug_assert_eq!(experimental_bytes.len(), experimental_bytes.capacity()); let tlv_stream = TlvStream::new(&bytes).chain(TlvStream::new(&experimental_bytes)); @@ -904,6 +908,7 @@ impl Bolt12Invoice { let ( payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream, experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream, + experimental_invoice_tlv_stream, ) = self.contents.as_tlv_stream(); let signature_tlv_stream = SignatureTlvStreamRef { signature: Some(&self.signature), @@ -911,7 +916,7 @@ impl Bolt12Invoice { ( payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream, signature_tlv_stream, experimental_offer_tlv_stream, - experimental_invoice_request_tlv_stream, + experimental_invoice_request_tlv_stream, experimental_invoice_tlv_stream, ) } @@ -1179,9 +1184,12 @@ impl InvoiceContents { InvoiceContents::ForOffer { invoice_request, .. } => invoice_request.as_tlv_stream(), InvoiceContents::ForRefund { refund, .. } => refund.as_tlv_stream(), }; - let invoice = self.fields().as_tlv_stream(); + let (invoice, experimental_invoice) = self.fields().as_tlv_stream(); - (payer, offer, invoice_request, invoice, experimental_offer, experimental_invoice_request) + ( + payer, offer, invoice_request, invoice, experimental_offer, + experimental_invoice_request, experimental_invoice, + ) } } @@ -1229,24 +1237,27 @@ pub(super) fn filter_fallbacks( } impl InvoiceFields { - fn as_tlv_stream(&self) -> InvoiceTlvStreamRef { + fn as_tlv_stream(&self) -> (InvoiceTlvStreamRef, ExperimentalInvoiceTlvStreamRef) { let features = { if self.features == Bolt12InvoiceFeatures::empty() { None } else { Some(&self.features) } }; - InvoiceTlvStreamRef { - paths: Some(Iterable(self.payment_paths.iter().map(|path| path.inner_blinded_path()))), - blindedpay: Some(Iterable(self.payment_paths.iter().map(|path| &path.payinfo))), - created_at: Some(self.created_at.as_secs()), - relative_expiry: self.relative_expiry.map(|duration| duration.as_secs() as u32), - payment_hash: Some(&self.payment_hash), - amount: Some(self.amount_msats), - fallbacks: self.fallbacks.as_ref(), - features, - node_id: Some(&self.signing_pubkey), - message_paths: None, - } + ( + InvoiceTlvStreamRef { + paths: Some(Iterable(self.payment_paths.iter().map(|path| path.inner_blinded_path()))), + blindedpay: Some(Iterable(self.payment_paths.iter().map(|path| &path.payinfo))), + created_at: Some(self.created_at.as_secs()), + relative_expiry: self.relative_expiry.map(|duration| duration.as_secs() as u32), + payment_hash: Some(&self.payment_hash), + amount: Some(self.amount_msats), + fallbacks: self.fallbacks.as_ref(), + features, + node_id: Some(&self.signing_pubkey), + message_paths: None, + }, + ExperimentalInvoiceTlvStreamRef {}, + ) } } @@ -1321,6 +1332,13 @@ tlv_stream!(InvoiceTlvStream, InvoiceTlvStreamRef<'a>, INVOICE_TYPES, { (236, message_paths: (Vec, WithoutLength)), }); +/// Valid type range for experimental invoice TLV records. +const EXPERIMENTAL_INVOICE_TYPES: core::ops::RangeFrom = 3_000_000_000..; + +tlv_stream!( + ExperimentalInvoiceTlvStream, ExperimentalInvoiceTlvStreamRef, EXPERIMENTAL_INVOICE_TYPES, {} +); + pub(super) type BlindedPathIter<'a> = core::iter::Map< core::slice::Iter<'a, BlindedPaymentPath>, for<'r> fn(&'r BlindedPaymentPath) -> &'r BlindedPath, @@ -1342,7 +1360,7 @@ impl_writeable!(FallbackAddress, { version, program }); type FullInvoiceTlvStream =( PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, InvoiceTlvStream, SignatureTlvStream, - ExperimentalOfferTlvStream, ExperimentalInvoiceRequestTlvStream, + ExperimentalOfferTlvStream, ExperimentalInvoiceRequestTlvStream, ExperimentalInvoiceTlvStream, ); type FullInvoiceTlvStreamRef<'a> = ( @@ -1353,6 +1371,7 @@ type FullInvoiceTlvStreamRef<'a> = ( SignatureTlvStreamRef<'a>, ExperimentalOfferTlvStreamRef, ExperimentalInvoiceRequestTlvStreamRef, + ExperimentalInvoiceTlvStreamRef, ); impl CursorReadable for FullInvoiceTlvStream { @@ -1364,11 +1383,12 @@ impl CursorReadable for FullInvoiceTlvStream { let signature = CursorReadable::read(r)?; let experimental_offer = CursorReadable::read(r)?; let experimental_invoice_request = CursorReadable::read(r)?; + let experimental_invoice = CursorReadable::read(r)?; Ok( ( payer, offer, invoice_request, invoice, signature, experimental_offer, - experimental_invoice_request, + experimental_invoice_request, experimental_invoice, ) ) } @@ -1376,7 +1396,7 @@ impl CursorReadable for FullInvoiceTlvStream { type PartialInvoiceTlvStream = ( PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, InvoiceTlvStream, - ExperimentalOfferTlvStream, ExperimentalInvoiceRequestTlvStream, + ExperimentalOfferTlvStream, ExperimentalInvoiceRequestTlvStream, ExperimentalInvoiceTlvStream, ); type PartialInvoiceTlvStreamRef<'a> = ( @@ -1386,6 +1406,7 @@ type PartialInvoiceTlvStreamRef<'a> = ( InvoiceTlvStreamRef<'a>, ExperimentalOfferTlvStreamRef, ExperimentalInvoiceRequestTlvStreamRef, + ExperimentalInvoiceTlvStreamRef, ); impl CursorReadable for PartialInvoiceTlvStream { @@ -1396,11 +1417,12 @@ impl CursorReadable for PartialInvoiceTlvStream { let invoice = CursorReadable::read(r)?; let experimental_offer = CursorReadable::read(r)?; let experimental_invoice_request = CursorReadable::read(r)?; + let experimental_invoice = CursorReadable::read(r)?; Ok( ( payer, offer, invoice_request, invoice, experimental_offer, - experimental_invoice_request, + experimental_invoice_request, experimental_invoice, ) ) } @@ -1416,11 +1438,13 @@ impl TryFrom> for Bolt12Invoice { SignatureTlvStream { signature }, experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream, + experimental_invoice_tlv_stream, ) = tlv_stream; let contents = InvoiceContents::try_from( ( payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream, experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream, + experimental_invoice_tlv_stream, ) )?; @@ -1449,6 +1473,7 @@ impl TryFrom for InvoiceContents { }, experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream, + ExperimentalInvoiceTlvStream {}, ) = tlv_stream; if message_paths.is_some() { return Err(Bolt12SemanticError::UnexpectedPaths) } @@ -1545,7 +1570,7 @@ pub(super) fn check_invoice_signing_pubkey( #[cfg(test)] mod tests { - use super::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, FallbackAddress, FullInvoiceTlvStreamRef, INVOICE_TYPES, InvoiceTlvStreamRef, SIGNATURE_TAG, UnsignedBolt12Invoice}; + use super::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, ExperimentalInvoiceTlvStreamRef, FallbackAddress, FullInvoiceTlvStreamRef, INVOICE_TYPES, InvoiceTlvStreamRef, SIGNATURE_TAG, UnsignedBolt12Invoice}; use bitcoin::{CompressedPublicKey, WitnessProgram, WitnessVersion}; use bitcoin::constants::ChainHash; @@ -1741,6 +1766,7 @@ mod tests { ExperimentalInvoiceRequestTlvStreamRef { experimental_bar: None, }, + ExperimentalInvoiceTlvStreamRef {}, ), ); @@ -1840,6 +1866,7 @@ mod tests { ExperimentalInvoiceRequestTlvStreamRef { experimental_bar: None, }, + ExperimentalInvoiceTlvStreamRef {}, ), ); @@ -2036,7 +2063,7 @@ mod tests { .relative_expiry(one_hour.as_secs() as u32) .build().unwrap() .sign(recipient_sign).unwrap(); - let (_, _, _, tlv_stream, _, _, _) = invoice.as_tlv_stream(); + let (_, _, _, tlv_stream, _, _, _, _) = invoice.as_tlv_stream(); #[cfg(feature = "std")] assert!(!invoice.is_expired()); assert_eq!(invoice.relative_expiry(), one_hour); @@ -2052,7 +2079,7 @@ mod tests { .relative_expiry(one_hour.as_secs() as u32 - 1) .build().unwrap() .sign(recipient_sign).unwrap(); - let (_, _, _, tlv_stream, _, _, _) = invoice.as_tlv_stream(); + let (_, _, _, tlv_stream, _, _, _, _) = invoice.as_tlv_stream(); #[cfg(feature = "std")] assert!(invoice.is_expired()); assert_eq!(invoice.relative_expiry(), one_hour - Duration::from_secs(1)); @@ -2071,7 +2098,7 @@ mod tests { .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .build().unwrap() .sign(recipient_sign).unwrap(); - let (_, _, _, tlv_stream, _, _, _) = invoice.as_tlv_stream(); + let (_, _, _, tlv_stream, _, _, _, _) = invoice.as_tlv_stream(); assert_eq!(invoice.amount_msats(), 1001); assert_eq!(tlv_stream.amount, Some(1001)); } @@ -2089,7 +2116,7 @@ mod tests { .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .build().unwrap() .sign(recipient_sign).unwrap(); - let (_, _, _, tlv_stream, _, _, _) = invoice.as_tlv_stream(); + let (_, _, _, tlv_stream, _, _, _, _) = invoice.as_tlv_stream(); assert_eq!(invoice.amount_msats(), 2000); assert_eq!(tlv_stream.amount, Some(2000)); @@ -2127,7 +2154,7 @@ mod tests { .fallback_v1_p2tr_tweaked(&tweaked_pubkey) .build().unwrap() .sign(recipient_sign).unwrap(); - let (_, _, _, tlv_stream, _, _, _) = invoice.as_tlv_stream(); + let (_, _, _, tlv_stream, _, _, _, _) = invoice.as_tlv_stream(); assert_eq!( invoice.fallbacks(), vec![ @@ -2170,7 +2197,7 @@ mod tests { .allow_mpp() .build().unwrap() .sign(recipient_sign).unwrap(); - let (_, _, _, tlv_stream, _, _, _) = invoice.as_tlv_stream(); + let (_, _, _, tlv_stream, _, _, _, _) = invoice.as_tlv_stream(); assert_eq!(invoice.invoice_features(), &features); assert_eq!(tlv_stream.features, Some(&features)); } diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index ba66297f3..55fe9af11 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -1565,7 +1565,7 @@ mod tests { let ( payer_tlv_stream, offer_tlv_stream, mut invoice_request_tlv_stream, mut invoice_tlv_stream, mut signature_tlv_stream, experimental_offer_tlv_stream, - experimental_invoice_request_tlv_stream, + experimental_invoice_request_tlv_stream, experimental_invoice_tlv_stream, ) = invoice.as_tlv_stream(); invoice_request_tlv_stream.amount = Some(2000); invoice_tlv_stream.amount = Some(2000); @@ -1574,6 +1574,7 @@ mod tests { (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream); let experimental_tlv_stream = ( experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream, + experimental_invoice_tlv_stream, ); let mut bytes = Vec::new(); (&tlv_stream, &experimental_tlv_stream).write(&mut bytes).unwrap(); @@ -1594,7 +1595,7 @@ mod tests { let ( mut payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream, mut signature_tlv_stream, experimental_offer_tlv_stream, - experimental_invoice_request_tlv_stream, + experimental_invoice_request_tlv_stream, experimental_invoice_tlv_stream, ) = invoice.as_tlv_stream(); let metadata = payer_tlv_stream.metadata.unwrap().iter().copied().rev().collect(); payer_tlv_stream.metadata = Some(&metadata); @@ -1603,6 +1604,7 @@ mod tests { (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream); let experimental_tlv_stream = ( experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream, + experimental_invoice_tlv_stream, ); let mut bytes = Vec::new(); (&tlv_stream, &experimental_tlv_stream).write(&mut bytes).unwrap(); @@ -1652,7 +1654,7 @@ mod tests { let ( payer_tlv_stream, offer_tlv_stream, mut invoice_request_tlv_stream, mut invoice_tlv_stream, mut signature_tlv_stream, experimental_offer_tlv_stream, - experimental_invoice_request_tlv_stream, + experimental_invoice_request_tlv_stream, experimental_invoice_tlv_stream, ) = invoice.as_tlv_stream(); invoice_request_tlv_stream.amount = Some(2000); invoice_tlv_stream.amount = Some(2000); @@ -1661,6 +1663,7 @@ mod tests { (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream); let experimental_tlv_stream = ( experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream, + experimental_invoice_tlv_stream, ); let mut bytes = Vec::new(); (&tlv_stream, &experimental_tlv_stream).write(&mut bytes).unwrap(); @@ -1683,7 +1686,7 @@ mod tests { let ( payer_tlv_stream, offer_tlv_stream, mut invoice_request_tlv_stream, invoice_tlv_stream, mut signature_tlv_stream, experimental_offer_tlv_stream, - experimental_invoice_request_tlv_stream, + experimental_invoice_request_tlv_stream, experimental_invoice_tlv_stream, ) = invoice.as_tlv_stream(); let payer_id = pubkey(1); invoice_request_tlv_stream.payer_id = Some(&payer_id); @@ -1692,6 +1695,7 @@ mod tests { (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream); let experimental_tlv_stream = ( experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream, + experimental_invoice_tlv_stream, ); let mut bytes = Vec::new(); (&tlv_stream, &experimental_tlv_stream).write(&mut bytes).unwrap(); diff --git a/lightning/src/offers/static_invoice.rs b/lightning/src/offers/static_invoice.rs index a2ea4032c..56ef40aab 100644 --- a/lightning/src/offers/static_invoice.rs +++ b/lightning/src/offers/static_invoice.rs @@ -16,7 +16,8 @@ use crate::ln::features::{Bolt12InvoiceFeatures, OfferFeatures}; use crate::ln::inbound_payment::ExpandedKey; use crate::ln::msgs::DecodeError; use crate::offers::invoice::{ - check_invoice_signing_pubkey, construct_payment_paths, filter_fallbacks, FallbackAddress, + check_invoice_signing_pubkey, construct_payment_paths, filter_fallbacks, + ExperimentalInvoiceTlvStream, ExperimentalInvoiceTlvStreamRef, FallbackAddress, InvoiceTlvStream, InvoiceTlvStreamRef, }; use crate::offers::invoice_macros::{invoice_accessors_common, invoice_builder_methods_common}; @@ -278,14 +279,17 @@ macro_rules! invoice_accessors_signing_pubkey { impl UnsignedStaticInvoice { fn new(offer_bytes: &Vec, contents: InvoiceContents) -> Self { - let (_, invoice_tlv_stream, _) = contents.as_tlv_stream(); + let (_, invoice_tlv_stream, _, experimental_invoice_tlv_stream) = contents.as_tlv_stream(); // Allocate enough space for the invoice, which will include: // - all TLV records from `offer_bytes`, // - all invoice-specific TLV records, and // - a signature TLV record once the invoice is signed. let mut bytes = Vec::with_capacity( - offer_bytes.len() + invoice_tlv_stream.serialized_length() + SIGNATURE_TLV_RECORD_SIZE, + offer_bytes.len() + + invoice_tlv_stream.serialized_length() + + SIGNATURE_TLV_RECORD_SIZE + + experimental_invoice_tlv_stream.serialized_length(), ); // Use the offer bytes instead of the offer TLV stream as the latter may have contained @@ -304,13 +308,15 @@ impl UnsignedStaticInvoice { remaining_bytes.len() - experimental_tlv_stream .peek() - .map_or(remaining_bytes.len(), |first_record| first_record.start), + .map_or(remaining_bytes.len(), |first_record| first_record.start) + + experimental_invoice_tlv_stream.serialized_length(), ); for record in experimental_tlv_stream { record.write(&mut experimental_bytes).unwrap(); } + experimental_invoice_tlv_stream.write(&mut experimental_bytes).unwrap(); debug_assert_eq!(experimental_bytes.len(), experimental_bytes.capacity()); let tlv_stream = TlvStream::new(&bytes).chain(TlvStream::new(&experimental_bytes)); @@ -445,9 +451,11 @@ impl InvoiceContents { payment_hash: None, }; + let experimental_invoice = ExperimentalInvoiceTlvStreamRef {}; + let (offer, experimental_offer) = self.offer.as_tlv_stream(); - (offer, invoice, experimental_offer) + (offer, invoice, experimental_offer, experimental_invoice) } fn chain(&self) -> ChainHash { @@ -544,8 +552,13 @@ impl TryFrom> for StaticInvoice { } } -type FullInvoiceTlvStream = - (OfferTlvStream, InvoiceTlvStream, SignatureTlvStream, ExperimentalOfferTlvStream); +type FullInvoiceTlvStream = ( + OfferTlvStream, + InvoiceTlvStream, + SignatureTlvStream, + ExperimentalOfferTlvStream, + ExperimentalInvoiceTlvStream, +); impl CursorReadable for FullInvoiceTlvStream { fn read>(r: &mut io::Cursor) -> Result { @@ -553,15 +566,21 @@ impl CursorReadable for FullInvoiceTlvStream { let invoice = CursorReadable::read(r)?; let signature = CursorReadable::read(r)?; let experimental_offer = CursorReadable::read(r)?; + let experimental_invoice = CursorReadable::read(r)?; - Ok((offer, invoice, signature, experimental_offer)) + Ok((offer, invoice, signature, experimental_offer, experimental_invoice)) } } -type PartialInvoiceTlvStream = (OfferTlvStream, InvoiceTlvStream, ExperimentalOfferTlvStream); +type PartialInvoiceTlvStream = + (OfferTlvStream, InvoiceTlvStream, ExperimentalOfferTlvStream, ExperimentalInvoiceTlvStream); -type PartialInvoiceTlvStreamRef<'a> = - (OfferTlvStreamRef<'a>, InvoiceTlvStreamRef<'a>, ExperimentalOfferTlvStreamRef); +type PartialInvoiceTlvStreamRef<'a> = ( + OfferTlvStreamRef<'a>, + InvoiceTlvStreamRef<'a>, + ExperimentalOfferTlvStreamRef, + ExperimentalInvoiceTlvStreamRef, +); impl TryFrom> for StaticInvoice { type Error = Bolt12ParseError; @@ -573,11 +592,13 @@ impl TryFrom> for StaticInvoice { invoice_tlv_stream, SignatureTlvStream { signature }, experimental_offer_tlv_stream, + experimental_invoice_tlv_stream, ) = tlv_stream; let contents = InvoiceContents::try_from(( offer_tlv_stream, invoice_tlv_stream, experimental_offer_tlv_stream, + experimental_invoice_tlv_stream, ))?; let signature = match signature { @@ -615,6 +636,7 @@ impl TryFrom for InvoiceContents { amount, }, experimental_offer_tlv_stream, + ExperimentalInvoiceTlvStream {}, ) = tlv_stream; if payment_hash.is_some() { @@ -666,7 +688,9 @@ mod tests { use crate::ln::features::{Bolt12InvoiceFeatures, OfferFeatures}; use crate::ln::inbound_payment::ExpandedKey; use crate::ln::msgs::DecodeError; - use crate::offers::invoice::{InvoiceTlvStreamRef, INVOICE_TYPES}; + use crate::offers::invoice::{ + ExperimentalInvoiceTlvStreamRef, InvoiceTlvStreamRef, INVOICE_TYPES, + }; use crate::offers::merkle; use crate::offers::merkle::{SignatureTlvStreamRef, TaggedHash}; use crate::offers::nonce::Nonce; @@ -691,17 +715,23 @@ mod tests { InvoiceTlvStreamRef<'a>, SignatureTlvStreamRef<'a>, ExperimentalOfferTlvStreamRef, + ExperimentalInvoiceTlvStreamRef, ); impl StaticInvoice { fn as_tlv_stream(&self) -> FullInvoiceTlvStreamRef { - let (offer_tlv_stream, invoice_tlv_stream, experimental_offer_tlv_stream) = - self.contents.as_tlv_stream(); + let ( + offer_tlv_stream, + invoice_tlv_stream, + experimental_offer_tlv_stream, + experimental_invoice_tlv_stream, + ) = self.contents.as_tlv_stream(); ( offer_tlv_stream, invoice_tlv_stream, SignatureTlvStreamRef { signature: Some(&self.signature) }, experimental_offer_tlv_stream, + experimental_invoice_tlv_stream, ) } } @@ -712,6 +742,7 @@ mod tests { InvoiceTlvStreamRef, SignatureTlvStreamRef, ExperimentalOfferTlvStreamRef, + ExperimentalInvoiceTlvStreamRef, ), ) -> Vec { let mut buffer = Vec::new(); @@ -719,6 +750,7 @@ mod tests { tlv_stream.1.write(&mut buffer).unwrap(); tlv_stream.2.write(&mut buffer).unwrap(); tlv_stream.3.write(&mut buffer).unwrap(); + tlv_stream.4.write(&mut buffer).unwrap(); buffer } @@ -849,6 +881,7 @@ mod tests { }, SignatureTlvStreamRef { signature: Some(&invoice.signature()) }, ExperimentalOfferTlvStreamRef { experimental_foo: None }, + ExperimentalInvoiceTlvStreamRef {}, ) ); diff --git a/lightning/src/util/ser.rs b/lightning/src/util/ser.rs index a3b56fa84..2c88f9134 100644 --- a/lightning/src/util/ser.rs +++ b/lightning/src/util/ser.rs @@ -1488,6 +1488,30 @@ impl Readable for (A, B, C, D, E, F, G) { + fn read(r: &mut R) -> Result { + let a: A = Readable::read(r)?; + let b: B = Readable::read(r)?; + let c: C = Readable::read(r)?; + let d: D = Readable::read(r)?; + let e: E = Readable::read(r)?; + let f: F = Readable::read(r)?; + let g: G = Readable::read(r)?; + Ok((a, b, c, d, e, f, g)) + } +} +impl Writeable for (A, B, C, D, E, F, G) { + fn write(&self, w: &mut W) -> Result<(), io::Error> { + self.0.write(w)?; + self.1.write(w)?; + self.2.write(w)?; + self.3.write(w)?; + self.4.write(w)?; + self.5.write(w)?; + self.6.write(w) + } +} + impl Writeable for () { fn write(&self, _: &mut W) -> Result<(), io::Error> { Ok(()) -- 2.39.5