From e01e731ff7d1bfc62531fb333ac03ccf399c8d9a Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Mon, 5 Aug 2024 18:51:32 -0500 Subject: [PATCH] Parse experimental offer TLV records The BOLT12 spec defines an experimental TLV range that are allowed in offer messages. Allow this range when parsing an offer and include those bytes in any invoice requests. Also include those bytes when computing an OfferId and verifying that an InvoiceRequest is for a valid Offer. --- lightning/src/offers/invoice.rs | 75 +++++++---- lightning/src/offers/invoice_request.rs | 112 ++++++++++------ lightning/src/offers/offer.rs | 167 ++++++++++++++---------- lightning/src/offers/refund.rs | 42 +++--- lightning/src/offers/static_invoice.rs | 66 +++++++--- lightning/src/util/ser.rs | 20 +++ 6 files changed, 309 insertions(+), 173 deletions(-) diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index 0dfe49cec..c8be0e377 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -124,7 +124,7 @@ use crate::offers::invoice_macros::invoice_builder_methods_test; use crate::offers::invoice_request::{EXPERIMENTAL_INVOICE_REQUEST_TYPES, INVOICE_REQUEST_PAYER_ID_TYPE, INVOICE_REQUEST_TYPES, IV_BYTES as INVOICE_REQUEST_IV_BYTES, InvoiceRequest, InvoiceRequestContents, InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef}; use crate::offers::merkle::{SignError, SignFn, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, TlvStream, self, SIGNATURE_TLV_RECORD_SIZE}; use crate::offers::nonce::Nonce; -use crate::offers::offer::{Amount, EXPERIMENTAL_OFFER_TYPES, OFFER_TYPES, OfferTlvStream, OfferTlvStreamRef, Quantity}; +use crate::offers::offer::{Amount, EXPERIMENTAL_OFFER_TYPES, ExperimentalOfferTlvStream, ExperimentalOfferTlvStreamRef, OFFER_TYPES, OfferTlvStream, OfferTlvStreamRef, Quantity}; use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError, ParsedMessage}; use crate::offers::payer::{PAYER_METADATA_TYPE, PayerTlvStream, PayerTlvStreamRef}; use crate::offers::refund::{IV_BYTES_WITH_METADATA as REFUND_IV_BYTES_WITH_METADATA, IV_BYTES_WITHOUT_METADATA as REFUND_IV_BYTES_WITHOUT_METADATA, Refund, RefundContents}; @@ -497,7 +497,7 @@ 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, _) = contents.as_tlv_stream(); // Allocate enough space for the invoice, which will include: // - all TLV records from `invreq_bytes` except signatures, @@ -901,13 +901,17 @@ impl Bolt12Invoice { } pub(crate) fn as_tlv_stream(&self) -> FullInvoiceTlvStreamRef { - let (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream) = - self.contents.as_tlv_stream(); + let ( + payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream, + experimental_offer_tlv_stream, + ) = self.contents.as_tlv_stream(); let signature_tlv_stream = SignatureTlvStreamRef { signature: Some(&self.signature), }; - (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream, - signature_tlv_stream) + ( + payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream, + signature_tlv_stream, experimental_offer_tlv_stream, + ) } pub(crate) fn is_for_refund_without_paths(&self) -> bool { @@ -1145,7 +1149,7 @@ impl InvoiceContents { fn verify( &self, bytes: &[u8], metadata: &Metadata, key: &ExpandedKey, iv_bytes: &[u8; IV_LEN], - secp_ctx: &Secp256k1 + secp_ctx: &Secp256k1, ) -> Result { const EXPERIMENTAL_TYPES: core::ops::Range = EXPERIMENTAL_OFFER_TYPES.start..EXPERIMENTAL_INVOICE_REQUEST_TYPES.end; @@ -1168,13 +1172,13 @@ impl InvoiceContents { } fn as_tlv_stream(&self) -> PartialInvoiceTlvStreamRef { - let (payer, offer, invoice_request) = match self { + let (payer, offer, invoice_request, experimental_offer) = match self { InvoiceContents::ForOffer { invoice_request, .. } => invoice_request.as_tlv_stream(), InvoiceContents::ForRefund { refund, .. } => refund.as_tlv_stream(), }; let invoice = self.fields().as_tlv_stream(); - (payer, offer, invoice_request, invoice) + (payer, offer, invoice_request, invoice, experimental_offer) } } @@ -1333,8 +1337,10 @@ pub(super) struct FallbackAddress { impl_writeable!(FallbackAddress, { version, program }); -type FullInvoiceTlvStream = - (PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, InvoiceTlvStream, SignatureTlvStream); +type FullInvoiceTlvStream =( + PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, InvoiceTlvStream, SignatureTlvStream, + ExperimentalOfferTlvStream, +); type FullInvoiceTlvStreamRef<'a> = ( PayerTlvStreamRef<'a>, @@ -1342,6 +1348,7 @@ type FullInvoiceTlvStreamRef<'a> = ( InvoiceRequestTlvStreamRef<'a>, InvoiceTlvStreamRef<'a>, SignatureTlvStreamRef<'a>, + ExperimentalOfferTlvStreamRef, ); impl CursorReadable for FullInvoiceTlvStream { @@ -1351,19 +1358,23 @@ impl CursorReadable for FullInvoiceTlvStream { let invoice_request = CursorReadable::read(r)?; let invoice = CursorReadable::read(r)?; let signature = CursorReadable::read(r)?; + let experimental_offer = CursorReadable::read(r)?; - Ok((payer, offer, invoice_request, invoice, signature)) + Ok((payer, offer, invoice_request, invoice, signature, experimental_offer)) } } -type PartialInvoiceTlvStream = - (PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, InvoiceTlvStream); +type PartialInvoiceTlvStream = ( + PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, InvoiceTlvStream, + ExperimentalOfferTlvStream, +); type PartialInvoiceTlvStreamRef<'a> = ( PayerTlvStreamRef<'a>, OfferTlvStreamRef<'a>, InvoiceRequestTlvStreamRef<'a>, InvoiceTlvStreamRef<'a>, + ExperimentalOfferTlvStreamRef, ); impl CursorReadable for PartialInvoiceTlvStream { @@ -1372,8 +1383,9 @@ impl CursorReadable for PartialInvoiceTlvStream { let offer = CursorReadable::read(r)?; let invoice_request = CursorReadable::read(r)?; let invoice = CursorReadable::read(r)?; + let experimental_offer = CursorReadable::read(r)?; - Ok((payer, offer, invoice_request, invoice)) + Ok((payer, offer, invoice_request, invoice, experimental_offer)) } } @@ -1385,9 +1397,13 @@ impl TryFrom> for Bolt12Invoice { let ( payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream, SignatureTlvStream { signature }, + experimental_offer_tlv_stream, ) = tlv_stream; let contents = InvoiceContents::try_from( - (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream) + ( + payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream, + experimental_offer_tlv_stream, + ) )?; let signature = signature.ok_or( @@ -1413,6 +1429,7 @@ impl TryFrom for InvoiceContents { paths, blindedpay, created_at, relative_expiry, payment_hash, amount, fallbacks, features, node_id, message_paths, }, + experimental_offer_tlv_stream, ) = tlv_stream; if message_paths.is_some() { return Err(Bolt12SemanticError::UnexpectedPaths) } @@ -1445,12 +1462,18 @@ impl TryFrom for InvoiceContents { if offer_tlv_stream.issuer_id.is_none() && offer_tlv_stream.paths.is_none() { let refund = RefundContents::try_from( - (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream) + ( + payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, + experimental_offer_tlv_stream, + ) )?; Ok(InvoiceContents::ForRefund { refund, fields }) } else { let invoice_request = InvoiceRequestContents::try_from( - (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream) + ( + payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, + experimental_offer_tlv_stream, + ) )?; Ok(InvoiceContents::ForOffer { invoice_request, fields }) } @@ -1525,7 +1548,7 @@ mod tests { use crate::offers::invoice_request::InvoiceRequestTlvStreamRef; use crate::offers::merkle::{SignError, SignatureTlvStreamRef, TaggedHash, self}; use crate::offers::nonce::Nonce; - use crate::offers::offer::{Amount, OfferTlvStreamRef, Quantity}; + use crate::offers::offer::{Amount, ExperimentalOfferTlvStreamRef, OfferTlvStreamRef, Quantity}; use crate::prelude::*; #[cfg(not(c_bindings))] use { @@ -1693,6 +1716,7 @@ mod tests { message_paths: None, }, SignatureTlvStreamRef { signature: Some(&invoice.signature()) }, + ExperimentalOfferTlvStreamRef {}, ), ); @@ -1786,6 +1810,7 @@ mod tests { message_paths: None, }, SignatureTlvStreamRef { signature: Some(&invoice.signature()) }, + ExperimentalOfferTlvStreamRef {}, ), ); @@ -1979,7 +2004,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); @@ -1995,7 +2020,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)); @@ -2014,7 +2039,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)); } @@ -2032,7 +2057,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)); @@ -2070,7 +2095,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![ @@ -2113,7 +2138,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 5ec4ab8a7..231c582d5 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -71,7 +71,7 @@ use crate::ln::inbound_payment::{ExpandedKey, IV_LEN}; use crate::ln::msgs::DecodeError; use crate::offers::merkle::{SignError, SignFn, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, TlvStream, self, SIGNATURE_TLV_RECORD_SIZE}; use crate::offers::nonce::Nonce; -use crate::offers::offer::{EXPERIMENTAL_OFFER_TYPES, OFFER_TYPES, Offer, OfferContents, OfferId, OfferTlvStream, OfferTlvStreamRef}; +use crate::offers::offer::{EXPERIMENTAL_OFFER_TYPES, ExperimentalOfferTlvStream, ExperimentalOfferTlvStreamRef, OFFER_TYPES, Offer, OfferContents, OfferId, OfferTlvStream, OfferTlvStreamRef}; use crate::offers::parse::{Bolt12ParseError, ParsedMessage, Bolt12SemanticError}; use crate::offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef}; use crate::offers::signer::{Metadata, MetadataMaterial}; @@ -523,6 +523,7 @@ impl UnsignedInvoiceRequest { // unknown TLV records, which are not stored in `OfferContents`. let ( payer_tlv_stream, _offer_tlv_stream, invoice_request_tlv_stream, + _experimental_offer_tlv_stream, ) = contents.as_tlv_stream(); // Allocate enough space for the invoice_request, which will include: @@ -907,12 +908,17 @@ impl InvoiceRequest { } pub(crate) fn as_tlv_stream(&self) -> FullInvoiceRequestTlvStreamRef { - let (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream) = - self.contents.as_tlv_stream(); + let ( + payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, + experimental_offer_tlv_stream, + ) = self.contents.as_tlv_stream(); let signature_tlv_stream = SignatureTlvStreamRef { signature: Some(&self.signature), }; - (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, signature_tlv_stream) + ( + payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, + signature_tlv_stream, experimental_offer_tlv_stream, + ) } } @@ -1028,9 +1034,9 @@ impl InvoiceRequestContents { } pub(super) fn as_tlv_stream(&self) -> PartialInvoiceRequestTlvStreamRef { - let (payer, offer, mut invoice_request) = self.inner.as_tlv_stream(); + let (payer, offer, mut invoice_request, experimental_offer) = self.inner.as_tlv_stream(); invoice_request.payer_id = Some(&self.payer_signing_pubkey); - (payer, offer, invoice_request) + (payer, offer, invoice_request, experimental_offer) } } @@ -1048,7 +1054,7 @@ impl InvoiceRequestContentsWithoutPayerSigningPubkey { metadata: self.payer.0.as_bytes(), }; - let offer = self.offer.as_tlv_stream(); + let (offer, experimental_offer) = self.offer.as_tlv_stream(); let features = { if self.features == InvoiceRequestFeatures::empty() { None } @@ -1065,7 +1071,7 @@ impl InvoiceRequestContentsWithoutPayerSigningPubkey { paths: None, }; - (payer, offer, invoice_request) + (payer, offer, invoice_request, experimental_offer) } } @@ -1120,14 +1126,17 @@ tlv_stream!(InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef<'a>, INVOICE_REQ pub(super) const EXPERIMENTAL_INVOICE_REQUEST_TYPES: core::ops::Range = 2_000_000_000..3_000_000_000; -type FullInvoiceRequestTlvStream = - (PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, SignatureTlvStream); +type FullInvoiceRequestTlvStream = ( + PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, SignatureTlvStream, + ExperimentalOfferTlvStream, +); type FullInvoiceRequestTlvStreamRef<'a> = ( PayerTlvStreamRef<'a>, OfferTlvStreamRef<'a>, InvoiceRequestTlvStreamRef<'a>, SignatureTlvStreamRef<'a>, + ExperimentalOfferTlvStreamRef, ); impl CursorReadable for FullInvoiceRequestTlvStream { @@ -1136,17 +1145,21 @@ impl CursorReadable for FullInvoiceRequestTlvStream { let offer = CursorReadable::read(r)?; let invoice_request = CursorReadable::read(r)?; let signature = CursorReadable::read(r)?; + let experimental_offer = CursorReadable::read(r)?; - Ok((payer, offer, invoice_request, signature)) + Ok((payer, offer, invoice_request, signature, experimental_offer)) } } -type PartialInvoiceRequestTlvStream = (PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream); +type PartialInvoiceRequestTlvStream = ( + PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, ExperimentalOfferTlvStream, +); type PartialInvoiceRequestTlvStreamRef<'a> = ( PayerTlvStreamRef<'a>, OfferTlvStreamRef<'a>, InvoiceRequestTlvStreamRef<'a>, + ExperimentalOfferTlvStreamRef, ); impl TryFrom> for UnsignedInvoiceRequest { @@ -1155,13 +1168,8 @@ impl TryFrom> for UnsignedInvoiceRequest { fn try_from(bytes: Vec) -> Result { let invoice_request = ParsedMessage::::try_from(bytes)?; let ParsedMessage { mut bytes, tlv_stream } = invoice_request; - let ( - payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, - ) = tlv_stream; - let contents = InvoiceRequestContents::try_from( - (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream) - )?; + let contents = InvoiceRequestContents::try_from(tlv_stream)?; let tagged_hash = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &bytes); let offset = TlvStream::new(&bytes) @@ -1183,9 +1191,13 @@ impl TryFrom> for InvoiceRequest { let ( payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, SignatureTlvStream { signature }, + experimental_offer_tlv_stream, ) = tlv_stream; let contents = InvoiceRequestContents::try_from( - (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream) + ( + payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, + experimental_offer_tlv_stream, + ) )?; let signature = match signature { @@ -1209,13 +1221,14 @@ impl TryFrom for InvoiceRequestContents { InvoiceRequestTlvStream { chain, amount, features, quantity, payer_id, payer_note, paths, }, + experimental_offer_tlv_stream, ) = tlv_stream; let payer = match metadata { None => return Err(Bolt12SemanticError::MissingPayerMetadata), Some(metadata) => PayerContents(Metadata::Bytes(metadata)), }; - let offer = OfferContents::try_from(offer_tlv_stream)?; + let offer = OfferContents::try_from((offer_tlv_stream, experimental_offer_tlv_stream))?; if !offer.supports_chain(chain.unwrap_or_else(|| offer.implied_chain())) { return Err(Bolt12SemanticError::UnsupportedChain); @@ -1312,7 +1325,7 @@ mod tests { use crate::offers::invoice::{Bolt12Invoice, SIGNATURE_TAG as INVOICE_SIGNATURE_TAG}; use crate::offers::merkle::{SignError, SignatureTlvStreamRef, TaggedHash, self}; use crate::offers::nonce::Nonce; - use crate::offers::offer::{Amount, OfferTlvStreamRef, Quantity}; + use crate::offers::offer::{Amount, ExperimentalOfferTlvStreamRef, OfferTlvStreamRef, Quantity}; #[cfg(not(c_bindings))] use { crate::offers::offer::OfferBuilder, @@ -1421,6 +1434,7 @@ mod tests { paths: None, }, SignatureTlvStreamRef { signature: Some(&invoice_request.signature()) }, + ExperimentalOfferTlvStreamRef {}, ), ); @@ -1491,7 +1505,7 @@ mod tests { // Fails verification with altered fields let ( payer_tlv_stream, offer_tlv_stream, mut invoice_request_tlv_stream, - mut invoice_tlv_stream, mut signature_tlv_stream + mut invoice_tlv_stream, mut signature_tlv_stream, experimental_offer_tlv_stream, ) = invoice.as_tlv_stream(); invoice_request_tlv_stream.amount = Some(2000); invoice_tlv_stream.amount = Some(2000); @@ -1500,13 +1514,16 @@ mod tests { (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream); let mut bytes = Vec::new(); tlv_stream.write(&mut bytes).unwrap(); + experimental_offer_tlv_stream.write(&mut bytes).unwrap(); let message = TaggedHash::from_valid_tlv_stream_bytes(INVOICE_SIGNATURE_TAG, &bytes); let signature = merkle::sign_message(recipient_sign, &message, recipient_pubkey()).unwrap(); signature_tlv_stream.signature = Some(&signature); - let mut encoded_invoice = bytes; + let mut encoded_invoice = Vec::new(); + tlv_stream.write(&mut encoded_invoice).unwrap(); signature_tlv_stream.write(&mut encoded_invoice).unwrap(); + experimental_offer_tlv_stream.write(&mut encoded_invoice).unwrap(); let invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap(); assert!(invoice.verify_using_metadata(&expanded_key, &secp_ctx).is_err()); @@ -1514,7 +1531,7 @@ mod tests { // Fails verification with altered metadata let ( mut payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream, - mut signature_tlv_stream + mut signature_tlv_stream, experimental_offer_tlv_stream, ) = invoice.as_tlv_stream(); let metadata = payer_tlv_stream.metadata.unwrap().iter().copied().rev().collect(); payer_tlv_stream.metadata = Some(&metadata); @@ -1523,13 +1540,16 @@ mod tests { (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream); let mut bytes = Vec::new(); tlv_stream.write(&mut bytes).unwrap(); + experimental_offer_tlv_stream.write(&mut bytes).unwrap(); let message = TaggedHash::from_valid_tlv_stream_bytes(INVOICE_SIGNATURE_TAG, &bytes); let signature = merkle::sign_message(recipient_sign, &message, recipient_pubkey()).unwrap(); signature_tlv_stream.signature = Some(&signature); - let mut encoded_invoice = bytes; + let mut encoded_invoice = Vec::new(); + tlv_stream.write(&mut encoded_invoice).unwrap(); signature_tlv_stream.write(&mut encoded_invoice).unwrap(); + experimental_offer_tlv_stream.write(&mut encoded_invoice).unwrap(); let invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap(); assert!(invoice.verify_using_metadata(&expanded_key, &secp_ctx).is_err()); @@ -1564,7 +1584,7 @@ mod tests { // Fails verification with altered fields let ( payer_tlv_stream, offer_tlv_stream, mut invoice_request_tlv_stream, - mut invoice_tlv_stream, mut signature_tlv_stream + mut invoice_tlv_stream, mut signature_tlv_stream, experimental_offer_tlv_stream, ) = invoice.as_tlv_stream(); invoice_request_tlv_stream.amount = Some(2000); invoice_tlv_stream.amount = Some(2000); @@ -1573,13 +1593,16 @@ mod tests { (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream); let mut bytes = Vec::new(); tlv_stream.write(&mut bytes).unwrap(); + experimental_offer_tlv_stream.write(&mut bytes).unwrap(); let message = TaggedHash::from_valid_tlv_stream_bytes(INVOICE_SIGNATURE_TAG, &bytes); let signature = merkle::sign_message(recipient_sign, &message, recipient_pubkey()).unwrap(); signature_tlv_stream.signature = Some(&signature); - let mut encoded_invoice = bytes; + let mut encoded_invoice = Vec::new(); + tlv_stream.write(&mut encoded_invoice).unwrap(); signature_tlv_stream.write(&mut encoded_invoice).unwrap(); + experimental_offer_tlv_stream.write(&mut encoded_invoice).unwrap(); let invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap(); assert!( @@ -1589,7 +1612,7 @@ mod tests { // Fails verification with altered payer id let ( payer_tlv_stream, offer_tlv_stream, mut invoice_request_tlv_stream, invoice_tlv_stream, - mut signature_tlv_stream + mut signature_tlv_stream, experimental_offer_tlv_stream, ) = invoice.as_tlv_stream(); let payer_id = pubkey(1); invoice_request_tlv_stream.payer_id = Some(&payer_id); @@ -1598,13 +1621,16 @@ mod tests { (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream); let mut bytes = Vec::new(); tlv_stream.write(&mut bytes).unwrap(); + experimental_offer_tlv_stream.write(&mut bytes).unwrap(); let message = TaggedHash::from_valid_tlv_stream_bytes(INVOICE_SIGNATURE_TAG, &bytes); let signature = merkle::sign_message(recipient_sign, &message, recipient_pubkey()).unwrap(); signature_tlv_stream.signature = Some(&signature); - let mut encoded_invoice = bytes; + let mut encoded_invoice = Vec::new(); + tlv_stream.write(&mut encoded_invoice).unwrap(); signature_tlv_stream.write(&mut encoded_invoice).unwrap(); + experimental_offer_tlv_stream.write(&mut encoded_invoice).unwrap(); let invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap(); assert!( @@ -1624,7 +1650,7 @@ mod tests { .chain(Network::Bitcoin).unwrap() .build().unwrap() .sign(payer_sign).unwrap(); - let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + let (_, _, tlv_stream, _, _) = invoice_request.as_tlv_stream(); assert_eq!(invoice_request.chain(), mainnet); assert_eq!(tlv_stream.chain, None); @@ -1636,7 +1662,7 @@ mod tests { .chain(Network::Testnet).unwrap() .build().unwrap() .sign(payer_sign).unwrap(); - let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + let (_, _, tlv_stream, _, _) = invoice_request.as_tlv_stream(); assert_eq!(invoice_request.chain(), testnet); assert_eq!(tlv_stream.chain, Some(&testnet)); @@ -1649,7 +1675,7 @@ mod tests { .chain(Network::Bitcoin).unwrap() .build().unwrap() .sign(payer_sign).unwrap(); - let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + let (_, _, tlv_stream, _, _) = invoice_request.as_tlv_stream(); assert_eq!(invoice_request.chain(), mainnet); assert_eq!(tlv_stream.chain, None); @@ -1663,7 +1689,7 @@ mod tests { .chain(Network::Testnet).unwrap() .build().unwrap() .sign(payer_sign).unwrap(); - let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + let (_, _, tlv_stream, _, _) = invoice_request.as_tlv_stream(); assert_eq!(invoice_request.chain(), testnet); assert_eq!(tlv_stream.chain, Some(&testnet)); @@ -1699,7 +1725,7 @@ mod tests { .amount_msats(1000).unwrap() .build().unwrap() .sign(payer_sign).unwrap(); - let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + let (_, _, tlv_stream, _, _) = invoice_request.as_tlv_stream(); assert_eq!(invoice_request.amount_msats(), Some(1000)); assert_eq!(tlv_stream.amount, Some(1000)); @@ -1711,7 +1737,7 @@ mod tests { .amount_msats(1000).unwrap() .build().unwrap() .sign(payer_sign).unwrap(); - let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + let (_, _, tlv_stream, _, _) = invoice_request.as_tlv_stream(); assert_eq!(invoice_request.amount_msats(), Some(1000)); assert_eq!(tlv_stream.amount, Some(1000)); @@ -1722,7 +1748,7 @@ mod tests { .amount_msats(1001).unwrap() .build().unwrap() .sign(payer_sign).unwrap(); - let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + let (_, _, tlv_stream, _, _) = invoice_request.as_tlv_stream(); assert_eq!(invoice_request.amount_msats(), Some(1001)); assert_eq!(tlv_stream.amount, Some(1001)); @@ -1802,7 +1828,7 @@ mod tests { .features_unchecked(InvoiceRequestFeatures::unknown()) .build().unwrap() .sign(payer_sign).unwrap(); - let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + let (_, _, tlv_stream, _, _) = invoice_request.as_tlv_stream(); assert_eq!(invoice_request.invoice_request_features(), &InvoiceRequestFeatures::unknown()); assert_eq!(tlv_stream.features, Some(&InvoiceRequestFeatures::unknown())); @@ -1814,7 +1840,7 @@ mod tests { .features_unchecked(InvoiceRequestFeatures::empty()) .build().unwrap() .sign(payer_sign).unwrap(); - let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + let (_, _, tlv_stream, _, _) = invoice_request.as_tlv_stream(); assert_eq!(invoice_request.invoice_request_features(), &InvoiceRequestFeatures::empty()); assert_eq!(tlv_stream.features, None); } @@ -1831,7 +1857,7 @@ mod tests { .request_invoice(vec![1; 32], payer_pubkey()).unwrap() .build().unwrap() .sign(payer_sign).unwrap(); - let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + let (_, _, tlv_stream, _, _) = invoice_request.as_tlv_stream(); assert_eq!(invoice_request.quantity(), None); assert_eq!(tlv_stream.quantity, None); @@ -1856,7 +1882,7 @@ mod tests { .quantity(10).unwrap() .build().unwrap() .sign(payer_sign).unwrap(); - let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + let (_, _, tlv_stream, _, _) = invoice_request.as_tlv_stream(); assert_eq!(invoice_request.amount_msats(), Some(10_000)); assert_eq!(tlv_stream.amount, Some(10_000)); @@ -1881,7 +1907,7 @@ mod tests { .quantity(2).unwrap() .build().unwrap() .sign(payer_sign).unwrap(); - let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + let (_, _, tlv_stream, _, _) = invoice_request.as_tlv_stream(); assert_eq!(invoice_request.amount_msats(), Some(2_000)); assert_eq!(tlv_stream.amount, Some(2_000)); @@ -1917,7 +1943,7 @@ mod tests { .payer_note("bar".into()) .build().unwrap() .sign(payer_sign).unwrap(); - let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + let (_, _, tlv_stream, _, _) = invoice_request.as_tlv_stream(); assert_eq!(invoice_request.payer_note(), Some(PrintableString("bar"))); assert_eq!(tlv_stream.payer_note, Some(&String::from("bar"))); @@ -1929,7 +1955,7 @@ mod tests { .payer_note("baz".into()) .build().unwrap() .sign(payer_sign).unwrap(); - let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + let (_, _, tlv_stream, _, _) = invoice_request.as_tlv_stream(); assert_eq!(invoice_request.payer_note(), Some(PrintableString("baz"))); assert_eq!(tlv_stream.payer_note, Some(&String::from("baz"))); } diff --git a/lightning/src/offers/offer.rs b/lightning/src/offers/offer.rs index 810787265..1c7eac616 100644 --- a/lightning/src/offers/offer.rs +++ b/lightning/src/offers/offer.rs @@ -94,7 +94,7 @@ use crate::offers::merkle::{TaggedHash, TlvRecord, TlvStream}; use crate::offers::nonce::Nonce; use crate::offers::parse::{Bech32Encode, Bolt12ParseError, Bolt12SemanticError, ParsedMessage}; use crate::offers::signer::{Metadata, MetadataMaterial, self}; -use crate::util::ser::{HighZeroBytesDroppedBigSize, Readable, WithoutLength, Writeable, Writer}; +use crate::util::ser::{CursorReadable, HighZeroBytesDroppedBigSize, Readable, WithoutLength, Writeable, Writer}; use crate::util::string::PrintableString; #[cfg(not(c_bindings))] @@ -400,10 +400,10 @@ macro_rules! offer_builder_methods { ( }; let mut tlv_stream = $self.offer.as_tlv_stream(); - debug_assert_eq!(tlv_stream.metadata, None); - tlv_stream.metadata = None; + debug_assert_eq!(tlv_stream.0.metadata, None); + tlv_stream.0.metadata = None; if metadata.derives_recipient_keys() { - tlv_stream.issuer_id = None; + tlv_stream.0.issuer_id = None; } // Either replace the signing pubkey with the derived pubkey or include the metadata @@ -691,6 +691,7 @@ impl Offer { bytes: &'a [u8] ) -> impl core::iter::Iterator> { TlvStream::new(bytes).range(OFFER_TYPES) + .chain(TlvStream::new(bytes).range(EXPERIMENTAL_OFFER_TYPES)) } #[cfg(async_payments)] @@ -795,7 +796,7 @@ impl Offer { #[cfg(test)] impl Offer { - pub(super) fn as_tlv_stream(&self) -> OfferTlvStreamRef { + pub(super) fn as_tlv_stream(&self) -> FullOfferTlvStreamRef { self.contents.as_tlv_stream() } } @@ -982,7 +983,7 @@ impl OfferContents { } } - pub(super) fn as_tlv_stream(&self) -> OfferTlvStreamRef { + pub(super) fn as_tlv_stream(&self) -> FullOfferTlvStreamRef { let (currency, amount) = match &self.amount { None => (None, None), Some(Amount::Bitcoin { amount_msats }) => (None, Some(*amount_msats)), @@ -995,7 +996,7 @@ impl OfferContents { if self.features == OfferFeatures::empty() { None } else { Some(&self.features) } }; - OfferTlvStreamRef { + let offer = OfferTlvStreamRef { chains: self.chains.as_ref(), metadata: self.metadata(), currency, @@ -1007,7 +1008,11 @@ impl OfferContents { issuer: self.issuer.as_ref(), quantity_max: self.supported_quantity.to_tlv_record(), issuer_id: self.issuer_signing_pubkey.as_ref(), - } + }; + + let experimental_offer = ExperimentalOfferTlvStreamRef {}; + + (offer, experimental_offer) } } @@ -1102,6 +1107,22 @@ tlv_stream!(OfferTlvStream, OfferTlvStreamRef<'a>, OFFER_TYPES, { /// Valid type range for experimental offer TLV records. pub(super) const EXPERIMENTAL_OFFER_TYPES: core::ops::Range = 1_000_000_000..2_000_000_000; +tlv_stream!(ExperimentalOfferTlvStream, ExperimentalOfferTlvStreamRef, EXPERIMENTAL_OFFER_TYPES, { +}); + +type FullOfferTlvStream = (OfferTlvStream, ExperimentalOfferTlvStream); + +type FullOfferTlvStreamRef<'a> = (OfferTlvStreamRef<'a>, ExperimentalOfferTlvStreamRef); + +impl CursorReadable for FullOfferTlvStream { + fn read>(r: &mut io::Cursor) -> Result { + let offer = CursorReadable::read(r)?; + let experimental_offer = CursorReadable::read(r)?; + + Ok((offer, experimental_offer)) + } +} + impl Bech32Encode for Offer { const BECH32_HRP: &'static str = "lno"; } @@ -1118,7 +1139,7 @@ impl TryFrom> for Offer { type Error = Bolt12ParseError; fn try_from(bytes: Vec) -> Result { - let offer = ParsedMessage::::try_from(bytes)?; + let offer = ParsedMessage::::try_from(bytes)?; let ParsedMessage { bytes, tlv_stream } = offer; let contents = OfferContents::try_from(tlv_stream)?; let id = OfferId::from_valid_offer_tlv_stream(&bytes); @@ -1127,14 +1148,17 @@ impl TryFrom> for Offer { } } -impl TryFrom for OfferContents { +impl TryFrom for OfferContents { type Error = Bolt12SemanticError; - fn try_from(tlv_stream: OfferTlvStream) -> Result { - let OfferTlvStream { - chains, metadata, currency, amount, description, features, absolute_expiry, paths, - issuer, quantity_max, issuer_id, - } = tlv_stream; + fn try_from(tlv_stream: FullOfferTlvStream) -> Result { + let ( + OfferTlvStream { + chains, metadata, currency, amount, description, features, absolute_expiry, paths, + issuer, quantity_max, issuer_id, + }, + ExperimentalOfferTlvStream {}, + ) = tlv_stream; let metadata = metadata.map(|metadata| Metadata::Bytes(metadata)); @@ -1184,7 +1208,7 @@ impl core::fmt::Display for Offer { #[cfg(test)] mod tests { - use super::{Amount, OFFER_TYPES, Offer, OfferTlvStreamRef, Quantity}; + use super::{Amount, ExperimentalOfferTlvStreamRef, OFFER_TYPES, Offer, OfferTlvStreamRef, Quantity}; #[cfg(not(c_bindings))] use { super::OfferBuilder, @@ -1236,19 +1260,22 @@ mod tests { assert_eq!( offer.as_tlv_stream(), - OfferTlvStreamRef { - chains: None, - metadata: None, - currency: None, - amount: None, - description: None, - features: None, - absolute_expiry: None, - paths: None, - issuer: None, - quantity_max: None, - issuer_id: Some(&pubkey(42)), - }, + ( + OfferTlvStreamRef { + chains: None, + metadata: None, + currency: None, + amount: None, + description: None, + features: None, + absolute_expiry: None, + paths: None, + issuer: None, + quantity_max: None, + issuer_id: Some(&pubkey(42)), + }, + ExperimentalOfferTlvStreamRef {}, + ), ); if let Err(e) = Offer::try_from(buffer) { @@ -1267,7 +1294,7 @@ mod tests { .unwrap(); assert!(offer.supports_chain(mainnet)); assert_eq!(offer.chains(), vec![mainnet]); - assert_eq!(offer.as_tlv_stream().chains, None); + assert_eq!(offer.as_tlv_stream().0.chains, None); let offer = OfferBuilder::new(pubkey(42)) .chain(Network::Testnet) @@ -1275,7 +1302,7 @@ mod tests { .unwrap(); assert!(offer.supports_chain(testnet)); assert_eq!(offer.chains(), vec![testnet]); - assert_eq!(offer.as_tlv_stream().chains, Some(&vec![testnet])); + assert_eq!(offer.as_tlv_stream().0.chains, Some(&vec![testnet])); let offer = OfferBuilder::new(pubkey(42)) .chain(Network::Testnet) @@ -1284,7 +1311,7 @@ mod tests { .unwrap(); assert!(offer.supports_chain(testnet)); assert_eq!(offer.chains(), vec![testnet]); - assert_eq!(offer.as_tlv_stream().chains, Some(&vec![testnet])); + assert_eq!(offer.as_tlv_stream().0.chains, Some(&vec![testnet])); let offer = OfferBuilder::new(pubkey(42)) .chain(Network::Bitcoin) @@ -1294,7 +1321,7 @@ mod tests { assert!(offer.supports_chain(mainnet)); assert!(offer.supports_chain(testnet)); assert_eq!(offer.chains(), vec![mainnet, testnet]); - assert_eq!(offer.as_tlv_stream().chains, Some(&vec![mainnet, testnet])); + assert_eq!(offer.as_tlv_stream().0.chains, Some(&vec![mainnet, testnet])); } #[test] @@ -1304,7 +1331,7 @@ mod tests { .build() .unwrap(); assert_eq!(offer.metadata(), Some(&vec![42; 32])); - assert_eq!(offer.as_tlv_stream().metadata, Some(&vec![42; 32])); + assert_eq!(offer.as_tlv_stream().0.metadata, Some(&vec![42; 32])); let offer = OfferBuilder::new(pubkey(42)) .metadata(vec![42; 32]).unwrap() @@ -1312,7 +1339,7 @@ mod tests { .build() .unwrap(); assert_eq!(offer.metadata(), Some(&vec![43; 32])); - assert_eq!(offer.as_tlv_stream().metadata, Some(&vec![43; 32])); + assert_eq!(offer.as_tlv_stream().0.metadata, Some(&vec![43; 32])); } #[test] @@ -1349,7 +1376,7 @@ mod tests { // Fails verification with altered offer field let mut tlv_stream = offer.as_tlv_stream(); - tlv_stream.amount = Some(100); + tlv_stream.0.amount = Some(100); let mut encoded_offer = Vec::new(); tlv_stream.write(&mut encoded_offer).unwrap(); @@ -1362,8 +1389,8 @@ mod tests { // Fails verification with altered metadata let mut tlv_stream = offer.as_tlv_stream(); - let metadata = tlv_stream.metadata.unwrap().iter().copied().rev().collect(); - tlv_stream.metadata = Some(&metadata); + let metadata = tlv_stream.0.metadata.unwrap().iter().copied().rev().collect(); + tlv_stream.0.metadata = Some(&metadata); let mut encoded_offer = Vec::new(); tlv_stream.write(&mut encoded_offer).unwrap(); @@ -1416,7 +1443,7 @@ mod tests { // Fails verification with altered offer field let mut tlv_stream = offer.as_tlv_stream(); - tlv_stream.amount = Some(100); + tlv_stream.0.amount = Some(100); let mut encoded_offer = Vec::new(); tlv_stream.write(&mut encoded_offer).unwrap(); @@ -1432,7 +1459,7 @@ mod tests { // Fails verification with altered signing pubkey let mut tlv_stream = offer.as_tlv_stream(); let issuer_id = pubkey(1); - tlv_stream.issuer_id = Some(&issuer_id); + tlv_stream.0.issuer_id = Some(&issuer_id); let mut encoded_offer = Vec::new(); tlv_stream.write(&mut encoded_offer).unwrap(); @@ -1457,8 +1484,8 @@ mod tests { .unwrap(); let tlv_stream = offer.as_tlv_stream(); assert_eq!(offer.amount(), Some(bitcoin_amount)); - assert_eq!(tlv_stream.amount, Some(1000)); - assert_eq!(tlv_stream.currency, None); + assert_eq!(tlv_stream.0.amount, Some(1000)); + assert_eq!(tlv_stream.0.currency, None); #[cfg(not(c_bindings))] let builder = OfferBuilder::new(pubkey(42)) @@ -1469,8 +1496,8 @@ mod tests { builder.amount(currency_amount.clone()); let tlv_stream = builder.offer.as_tlv_stream(); assert_eq!(builder.offer.amount, Some(currency_amount.clone())); - assert_eq!(tlv_stream.amount, Some(10)); - assert_eq!(tlv_stream.currency, Some(b"USD")); + assert_eq!(tlv_stream.0.amount, Some(10)); + assert_eq!(tlv_stream.0.currency, Some(b"USD")); match builder.build() { Ok(_) => panic!("expected error"), Err(e) => assert_eq!(e, Bolt12SemanticError::UnsupportedCurrency), @@ -1482,8 +1509,8 @@ mod tests { .build() .unwrap(); let tlv_stream = offer.as_tlv_stream(); - assert_eq!(tlv_stream.amount, Some(1000)); - assert_eq!(tlv_stream.currency, None); + assert_eq!(tlv_stream.0.amount, Some(1000)); + assert_eq!(tlv_stream.0.currency, None); let invalid_amount = Amount::Bitcoin { amount_msats: MAX_VALUE_MSAT + 1 }; match OfferBuilder::new(pubkey(42)).amount(invalid_amount).build() { @@ -1499,7 +1526,7 @@ mod tests { .build() .unwrap(); assert_eq!(offer.description(), Some(PrintableString("foo"))); - assert_eq!(offer.as_tlv_stream().description, Some(&String::from("foo"))); + assert_eq!(offer.as_tlv_stream().0.description, Some(&String::from("foo"))); let offer = OfferBuilder::new(pubkey(42)) .description("foo".into()) @@ -1507,14 +1534,14 @@ mod tests { .build() .unwrap(); assert_eq!(offer.description(), Some(PrintableString("bar"))); - assert_eq!(offer.as_tlv_stream().description, Some(&String::from("bar"))); + assert_eq!(offer.as_tlv_stream().0.description, Some(&String::from("bar"))); let offer = OfferBuilder::new(pubkey(42)) .amount_msats(1000) .build() .unwrap(); assert_eq!(offer.description(), Some(PrintableString(""))); - assert_eq!(offer.as_tlv_stream().description, Some(&String::from(""))); + assert_eq!(offer.as_tlv_stream().0.description, Some(&String::from(""))); } #[test] @@ -1524,7 +1551,7 @@ mod tests { .build() .unwrap(); assert_eq!(offer.offer_features(), &OfferFeatures::unknown()); - assert_eq!(offer.as_tlv_stream().features, Some(&OfferFeatures::unknown())); + assert_eq!(offer.as_tlv_stream().0.features, Some(&OfferFeatures::unknown())); let offer = OfferBuilder::new(pubkey(42)) .features_unchecked(OfferFeatures::unknown()) @@ -1532,7 +1559,7 @@ mod tests { .build() .unwrap(); assert_eq!(offer.offer_features(), &OfferFeatures::empty()); - assert_eq!(offer.as_tlv_stream().features, None); + assert_eq!(offer.as_tlv_stream().0.features, None); } #[test] @@ -1549,7 +1576,7 @@ mod tests { assert!(!offer.is_expired()); assert!(!offer.is_expired_no_std(now)); assert_eq!(offer.absolute_expiry(), Some(future_expiry)); - assert_eq!(offer.as_tlv_stream().absolute_expiry, Some(future_expiry.as_secs())); + assert_eq!(offer.as_tlv_stream().0.absolute_expiry, Some(future_expiry.as_secs())); let offer = OfferBuilder::new(pubkey(42)) .absolute_expiry(future_expiry) @@ -1560,7 +1587,7 @@ mod tests { assert!(offer.is_expired()); assert!(offer.is_expired_no_std(now)); assert_eq!(offer.absolute_expiry(), Some(past_expiry)); - assert_eq!(offer.as_tlv_stream().absolute_expiry, Some(past_expiry.as_secs())); + assert_eq!(offer.as_tlv_stream().0.absolute_expiry, Some(past_expiry.as_secs())); } #[test] @@ -1591,8 +1618,8 @@ mod tests { assert_eq!(offer.paths(), paths.as_slice()); assert_eq!(offer.issuer_signing_pubkey(), Some(pubkey(42))); assert_ne!(pubkey(42), pubkey(44)); - assert_eq!(tlv_stream.paths, Some(&paths)); - assert_eq!(tlv_stream.issuer_id, Some(&pubkey(42))); + assert_eq!(tlv_stream.0.paths, Some(&paths)); + assert_eq!(tlv_stream.0.issuer_id, Some(&pubkey(42))); } #[test] @@ -1602,7 +1629,7 @@ mod tests { .build() .unwrap(); assert_eq!(offer.issuer(), Some(PrintableString("foo"))); - assert_eq!(offer.as_tlv_stream().issuer, Some(&String::from("foo"))); + assert_eq!(offer.as_tlv_stream().0.issuer, Some(&String::from("foo"))); let offer = OfferBuilder::new(pubkey(42)) .issuer("foo".into()) @@ -1610,7 +1637,7 @@ mod tests { .build() .unwrap(); assert_eq!(offer.issuer(), Some(PrintableString("bar"))); - assert_eq!(offer.as_tlv_stream().issuer, Some(&String::from("bar"))); + assert_eq!(offer.as_tlv_stream().0.issuer, Some(&String::from("bar"))); } #[test] @@ -1625,7 +1652,7 @@ mod tests { let tlv_stream = offer.as_tlv_stream(); assert!(!offer.expects_quantity()); assert_eq!(offer.supported_quantity(), Quantity::One); - assert_eq!(tlv_stream.quantity_max, None); + assert_eq!(tlv_stream.0.quantity_max, None); let offer = OfferBuilder::new(pubkey(42)) .supported_quantity(Quantity::Unbounded) @@ -1634,7 +1661,7 @@ mod tests { let tlv_stream = offer.as_tlv_stream(); assert!(offer.expects_quantity()); assert_eq!(offer.supported_quantity(), Quantity::Unbounded); - assert_eq!(tlv_stream.quantity_max, Some(0)); + assert_eq!(tlv_stream.0.quantity_max, Some(0)); let offer = OfferBuilder::new(pubkey(42)) .supported_quantity(Quantity::Bounded(ten)) @@ -1643,7 +1670,7 @@ mod tests { let tlv_stream = offer.as_tlv_stream(); assert!(offer.expects_quantity()); assert_eq!(offer.supported_quantity(), Quantity::Bounded(ten)); - assert_eq!(tlv_stream.quantity_max, Some(10)); + assert_eq!(tlv_stream.0.quantity_max, Some(10)); let offer = OfferBuilder::new(pubkey(42)) .supported_quantity(Quantity::Bounded(one)) @@ -1652,7 +1679,7 @@ mod tests { let tlv_stream = offer.as_tlv_stream(); assert!(offer.expects_quantity()); assert_eq!(offer.supported_quantity(), Quantity::Bounded(one)); - assert_eq!(tlv_stream.quantity_max, Some(1)); + assert_eq!(tlv_stream.0.quantity_max, Some(1)); let offer = OfferBuilder::new(pubkey(42)) .supported_quantity(Quantity::Bounded(ten)) @@ -1662,7 +1689,7 @@ mod tests { let tlv_stream = offer.as_tlv_stream(); assert!(!offer.expects_quantity()); assert_eq!(offer.supported_quantity(), Quantity::One); - assert_eq!(tlv_stream.quantity_max, None); + assert_eq!(tlv_stream.0.quantity_max, None); } #[test] @@ -1700,8 +1727,8 @@ mod tests { } let mut tlv_stream = offer.as_tlv_stream(); - tlv_stream.amount = Some(1000); - tlv_stream.currency = Some(b"USD"); + tlv_stream.0.amount = Some(1000); + tlv_stream.0.currency = Some(b"USD"); let mut encoded_offer = Vec::new(); tlv_stream.write(&mut encoded_offer).unwrap(); @@ -1711,8 +1738,8 @@ mod tests { } let mut tlv_stream = offer.as_tlv_stream(); - tlv_stream.amount = None; - tlv_stream.currency = Some(b"USD"); + tlv_stream.0.amount = None; + tlv_stream.0.currency = Some(b"USD"); let mut encoded_offer = Vec::new(); tlv_stream.write(&mut encoded_offer).unwrap(); @@ -1723,8 +1750,8 @@ mod tests { } let mut tlv_stream = offer.as_tlv_stream(); - tlv_stream.amount = Some(MAX_VALUE_MSAT + 1); - tlv_stream.currency = None; + tlv_stream.0.amount = Some(MAX_VALUE_MSAT + 1); + tlv_stream.0.currency = None; let mut encoded_offer = Vec::new(); tlv_stream.write(&mut encoded_offer).unwrap(); @@ -1751,7 +1778,7 @@ mod tests { } let mut tlv_stream = offer.as_tlv_stream(); - tlv_stream.description = None; + tlv_stream.0.description = None; let mut encoded_offer = Vec::new(); tlv_stream.write(&mut encoded_offer).unwrap(); @@ -1857,7 +1884,7 @@ mod tests { } let mut tlv_stream = offer.as_tlv_stream(); - tlv_stream.issuer_id = None; + tlv_stream.0.issuer_id = None; let mut encoded_offer = Vec::new(); tlv_stream.write(&mut encoded_offer).unwrap(); diff --git a/lightning/src/offers/refund.rs b/lightning/src/offers/refund.rs index 860de534c..8808c7c50 100644 --- a/lightning/src/offers/refund.rs +++ b/lightning/src/offers/refund.rs @@ -100,7 +100,7 @@ use crate::ln::inbound_payment::{ExpandedKey, IV_LEN}; use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT}; use crate::offers::invoice_request::{InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef}; use crate::offers::nonce::Nonce; -use crate::offers::offer::{OfferTlvStream, OfferTlvStreamRef}; +use crate::offers::offer::{ExperimentalOfferTlvStream, ExperimentalOfferTlvStreamRef, OfferTlvStream, OfferTlvStreamRef}; use crate::offers::parse::{Bech32Encode, Bolt12ParseError, Bolt12SemanticError, ParsedMessage}; use crate::offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef}; use crate::offers::signer::{Metadata, MetadataMaterial, self}; @@ -770,7 +770,9 @@ impl RefundContents { paths: self.paths.as_ref(), }; - (payer, offer, invoice_request) + let experimental_offer = ExperimentalOfferTlvStreamRef {}; + + (payer, offer, invoice_request, experimental_offer) } } @@ -793,12 +795,15 @@ impl Writeable for RefundContents { } } -type RefundTlvStream = (PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream); +type RefundTlvStream = ( + PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, ExperimentalOfferTlvStream, +); type RefundTlvStreamRef<'a> = ( PayerTlvStreamRef<'a>, OfferTlvStreamRef<'a>, InvoiceRequestTlvStreamRef<'a>, + ExperimentalOfferTlvStreamRef, ); impl CursorReadable for RefundTlvStream { @@ -806,8 +811,9 @@ impl CursorReadable for RefundTlvStream { let payer = CursorReadable::read(r)?; let offer = CursorReadable::read(r)?; let invoice_request = CursorReadable::read(r)?; + let experimental_offer = CursorReadable::read(r)?; - Ok((payer, offer, invoice_request)) + Ok((payer, offer, invoice_request, experimental_offer)) } } @@ -849,6 +855,7 @@ impl TryFrom for RefundContents { InvoiceRequestTlvStream { chain, amount, features, quantity, payer_id, payer_note, paths }, + _experimental_offer_tlv_stream, ) = tlv_stream; let payer = match payer_metadata { @@ -946,7 +953,7 @@ mod tests { use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT}; use crate::offers::invoice_request::{INVOICE_REQUEST_TYPES, InvoiceRequestTlvStreamRef}; use crate::offers::nonce::Nonce; - use crate::offers::offer::OfferTlvStreamRef; + use crate::offers::offer::{ExperimentalOfferTlvStreamRef, OfferTlvStreamRef}; use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError}; use crate::offers::payer::PayerTlvStreamRef; use crate::offers::test_utils::*; @@ -1014,6 +1021,7 @@ mod tests { payer_note: None, paths: None, }, + ExperimentalOfferTlvStreamRef {}, ), ); @@ -1166,7 +1174,7 @@ mod tests { .absolute_expiry(future_expiry) .build() .unwrap(); - let (_, tlv_stream, _) = refund.as_tlv_stream(); + let (_, tlv_stream, _, _) = refund.as_tlv_stream(); #[cfg(feature = "std")] assert!(!refund.is_expired()); assert!(!refund.is_expired_no_std(now)); @@ -1178,7 +1186,7 @@ mod tests { .absolute_expiry(past_expiry) .build() .unwrap(); - let (_, tlv_stream, _) = refund.as_tlv_stream(); + let (_, tlv_stream, _, _) = refund.as_tlv_stream(); #[cfg(feature = "std")] assert!(refund.is_expired()); assert!(refund.is_expired_no_std(now)); @@ -1210,7 +1218,7 @@ mod tests { .path(paths[1].clone()) .build() .unwrap(); - let (_, _, invoice_request_tlv_stream) = refund.as_tlv_stream(); + let (_, _, invoice_request_tlv_stream, _) = refund.as_tlv_stream(); assert_eq!(refund.payer_signing_pubkey(), pubkey(42)); assert_eq!(refund.paths(), paths.as_slice()); assert_ne!(pubkey(42), pubkey(44)); @@ -1224,7 +1232,7 @@ mod tests { .issuer("bar".into()) .build() .unwrap(); - let (_, tlv_stream, _) = refund.as_tlv_stream(); + let (_, tlv_stream, _, _) = refund.as_tlv_stream(); assert_eq!(refund.issuer(), Some(PrintableString("bar"))); assert_eq!(tlv_stream.issuer, Some(&String::from("bar"))); @@ -1233,7 +1241,7 @@ mod tests { .issuer("baz".into()) .build() .unwrap(); - let (_, tlv_stream, _) = refund.as_tlv_stream(); + let (_, tlv_stream, _, _) = refund.as_tlv_stream(); assert_eq!(refund.issuer(), Some(PrintableString("baz"))); assert_eq!(tlv_stream.issuer, Some(&String::from("baz"))); } @@ -1246,14 +1254,14 @@ mod tests { let refund = RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap() .chain(Network::Bitcoin) .build().unwrap(); - let (_, _, tlv_stream) = refund.as_tlv_stream(); + let (_, _, tlv_stream, _) = refund.as_tlv_stream(); assert_eq!(refund.chain(), mainnet); assert_eq!(tlv_stream.chain, None); let refund = RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap() .chain(Network::Testnet) .build().unwrap(); - let (_, _, tlv_stream) = refund.as_tlv_stream(); + let (_, _, tlv_stream, _) = refund.as_tlv_stream(); assert_eq!(refund.chain(), testnet); assert_eq!(tlv_stream.chain, Some(&testnet)); @@ -1261,7 +1269,7 @@ mod tests { .chain(Network::Regtest) .chain(Network::Testnet) .build().unwrap(); - let (_, _, tlv_stream) = refund.as_tlv_stream(); + let (_, _, tlv_stream, _) = refund.as_tlv_stream(); assert_eq!(refund.chain(), testnet); assert_eq!(tlv_stream.chain, Some(&testnet)); } @@ -1271,7 +1279,7 @@ mod tests { let refund = RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap() .quantity(10) .build().unwrap(); - let (_, _, tlv_stream) = refund.as_tlv_stream(); + let (_, _, tlv_stream, _) = refund.as_tlv_stream(); assert_eq!(refund.quantity(), Some(10)); assert_eq!(tlv_stream.quantity, Some(10)); @@ -1279,7 +1287,7 @@ mod tests { .quantity(10) .quantity(1) .build().unwrap(); - let (_, _, tlv_stream) = refund.as_tlv_stream(); + let (_, _, tlv_stream, _) = refund.as_tlv_stream(); assert_eq!(refund.quantity(), Some(1)); assert_eq!(tlv_stream.quantity, Some(1)); } @@ -1289,7 +1297,7 @@ mod tests { let refund = RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap() .payer_note("bar".into()) .build().unwrap(); - let (_, _, tlv_stream) = refund.as_tlv_stream(); + let (_, _, tlv_stream, _) = refund.as_tlv_stream(); assert_eq!(refund.payer_note(), Some(PrintableString("bar"))); assert_eq!(tlv_stream.payer_note, Some(&String::from("bar"))); @@ -1297,7 +1305,7 @@ mod tests { .payer_note("bar".into()) .payer_note("baz".into()) .build().unwrap(); - let (_, _, tlv_stream) = refund.as_tlv_stream(); + let (_, _, tlv_stream, _) = refund.as_tlv_stream(); assert_eq!(refund.payer_note(), Some(PrintableString("baz"))); assert_eq!(tlv_stream.payer_note, Some(&String::from("baz"))); } diff --git a/lightning/src/offers/static_invoice.rs b/lightning/src/offers/static_invoice.rs index b82a369ba..104588d92 100644 --- a/lightning/src/offers/static_invoice.rs +++ b/lightning/src/offers/static_invoice.rs @@ -27,8 +27,8 @@ use crate::offers::merkle::{ }; use crate::offers::nonce::Nonce; use crate::offers::offer::{ - Amount, Offer, OfferContents, OfferTlvStream, OfferTlvStreamRef, Quantity, - EXPERIMENTAL_OFFER_TYPES, OFFER_TYPES, + Amount, ExperimentalOfferTlvStream, ExperimentalOfferTlvStreamRef, Offer, OfferContents, + OfferTlvStream, OfferTlvStreamRef, Quantity, EXPERIMENTAL_OFFER_TYPES, OFFER_TYPES, }; use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError, ParsedMessage}; use crate::util::ser::{CursorReadable, Iterable, WithoutLength, Writeable, Writer}; @@ -278,7 +278,7 @@ 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, _) = contents.as_tlv_stream(); // Allocate enough space for the invoice, which will include: // - all TLV records from `offer_bytes`, @@ -445,7 +445,9 @@ impl InvoiceContents { payment_hash: None, }; - (self.offer.as_tlv_stream(), invoice) + let (offer, experimental_offer) = self.offer.as_tlv_stream(); + + (offer, invoice, experimental_offer) } fn chain(&self) -> ChainHash { @@ -542,29 +544,41 @@ impl TryFrom> for StaticInvoice { } } -type FullInvoiceTlvStream = (OfferTlvStream, InvoiceTlvStream, SignatureTlvStream); +type FullInvoiceTlvStream = + (OfferTlvStream, InvoiceTlvStream, SignatureTlvStream, ExperimentalOfferTlvStream); impl CursorReadable for FullInvoiceTlvStream { fn read>(r: &mut io::Cursor) -> Result { let offer = CursorReadable::read(r)?; let invoice = CursorReadable::read(r)?; let signature = CursorReadable::read(r)?; + let experimental_offer = CursorReadable::read(r)?; - Ok((offer, invoice, signature)) + Ok((offer, invoice, signature, experimental_offer)) } } -type PartialInvoiceTlvStream = (OfferTlvStream, InvoiceTlvStream); +type PartialInvoiceTlvStream = (OfferTlvStream, InvoiceTlvStream, ExperimentalOfferTlvStream); -type PartialInvoiceTlvStreamRef<'a> = (OfferTlvStreamRef<'a>, InvoiceTlvStreamRef<'a>); +type PartialInvoiceTlvStreamRef<'a> = + (OfferTlvStreamRef<'a>, InvoiceTlvStreamRef<'a>, ExperimentalOfferTlvStreamRef); impl TryFrom> for StaticInvoice { type Error = Bolt12ParseError; fn try_from(invoice: ParsedMessage) -> Result { let ParsedMessage { bytes, tlv_stream } = invoice; - let (offer_tlv_stream, invoice_tlv_stream, SignatureTlvStream { signature }) = tlv_stream; - let contents = InvoiceContents::try_from((offer_tlv_stream, invoice_tlv_stream))?; + let ( + offer_tlv_stream, + invoice_tlv_stream, + SignatureTlvStream { signature }, + experimental_offer_tlv_stream, + ) = tlv_stream; + let contents = InvoiceContents::try_from(( + offer_tlv_stream, + invoice_tlv_stream, + experimental_offer_tlv_stream, + ))?; let signature = match signature { None => { @@ -600,6 +614,7 @@ impl TryFrom for InvoiceContents { payment_hash, amount, }, + experimental_offer_tlv_stream, ) = tlv_stream; if payment_hash.is_some() { @@ -632,7 +647,7 @@ impl TryFrom for InvoiceContents { } Ok(InvoiceContents { - offer: OfferContents::try_from(offer_tlv_stream)?, + offer: OfferContents::try_from((offer_tlv_stream, experimental_offer_tlv_stream))?, payment_paths, message_paths, created_at, @@ -655,7 +670,9 @@ mod tests { use crate::offers::merkle; use crate::offers::merkle::{SignatureTlvStreamRef, TaggedHash}; use crate::offers::nonce::Nonce; - use crate::offers::offer::{Offer, OfferBuilder, OfferTlvStreamRef, Quantity}; + use crate::offers::offer::{ + ExperimentalOfferTlvStreamRef, Offer, OfferBuilder, OfferTlvStreamRef, Quantity, + }; use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError}; use crate::offers::static_invoice::{ StaticInvoice, StaticInvoiceBuilder, UnsignedStaticInvoice, DEFAULT_RELATIVE_EXPIRY, @@ -669,27 +686,39 @@ mod tests { use bitcoin::Network; use core::time::Duration; - type FullInvoiceTlvStreamRef<'a> = - (OfferTlvStreamRef<'a>, InvoiceTlvStreamRef<'a>, SignatureTlvStreamRef<'a>); + type FullInvoiceTlvStreamRef<'a> = ( + OfferTlvStreamRef<'a>, + InvoiceTlvStreamRef<'a>, + SignatureTlvStreamRef<'a>, + ExperimentalOfferTlvStreamRef, + ); impl StaticInvoice { fn as_tlv_stream(&self) -> FullInvoiceTlvStreamRef { - let (offer_tlv_stream, invoice_tlv_stream) = self.contents.as_tlv_stream(); + let (offer_tlv_stream, invoice_tlv_stream, experimental_offer_tlv_stream) = + self.contents.as_tlv_stream(); ( offer_tlv_stream, invoice_tlv_stream, SignatureTlvStreamRef { signature: Some(&self.signature) }, + experimental_offer_tlv_stream, ) } } fn tlv_stream_to_bytes( - tlv_stream: &(OfferTlvStreamRef, InvoiceTlvStreamRef, SignatureTlvStreamRef), + tlv_stream: &( + OfferTlvStreamRef, + InvoiceTlvStreamRef, + SignatureTlvStreamRef, + ExperimentalOfferTlvStreamRef, + ), ) -> Vec { let mut buffer = Vec::new(); tlv_stream.0.write(&mut buffer).unwrap(); tlv_stream.1.write(&mut buffer).unwrap(); tlv_stream.2.write(&mut buffer).unwrap(); + tlv_stream.3.write(&mut buffer).unwrap(); buffer } @@ -819,6 +848,7 @@ mod tests { message_paths: Some(&paths), }, SignatureTlvStreamRef { signature: Some(&invoice.signature()) }, + ExperimentalOfferTlvStreamRef {}, ) ); @@ -933,7 +963,7 @@ mod tests { // Error if offer paths are missing. let mut offer_without_paths = valid_offer.clone(); - let mut offer_tlv_stream = offer_without_paths.as_tlv_stream(); + let (mut offer_tlv_stream, _) = offer_without_paths.as_tlv_stream(); offer_tlv_stream.paths.take(); let mut buffer = Vec::new(); offer_tlv_stream.write(&mut buffer).unwrap(); @@ -969,7 +999,7 @@ mod tests { .unwrap(); let mut offer_missing_issuer_id = valid_offer.clone(); - let mut offer_tlv_stream = offer_missing_issuer_id.as_tlv_stream(); + let (mut offer_tlv_stream, _) = offer_missing_issuer_id.as_tlv_stream(); offer_tlv_stream.issuer_id.take(); let mut buffer = Vec::new(); offer_tlv_stream.write(&mut buffer).unwrap(); diff --git a/lightning/src/util/ser.rs b/lightning/src/util/ser.rs index 99d20b927..3e528ce93 100644 --- a/lightning/src/util/ser.rs +++ b/lightning/src/util/ser.rs @@ -1446,6 +1446,26 @@ impl Writeable for (A, B } } +impl Readable for (A, B, C, D, E) { + 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)?; + Ok((a, b, c, d, e)) + } +} +impl Writeable for (A, B, C, D, E) { + 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) + } +} + impl Writeable for () { fn write(&self, _: &mut W) -> Result<(), io::Error> { Ok(()) -- 2.39.5