From 6a708e2d9fa6fbdb592e1e9e856430182e1927dd Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Tue, 6 Aug 2024 16:21:32 -0500 Subject: [PATCH] Test verification with experimental offer TLVs Offer metadata is generated from the offer TLVs and should included those in the experimental range. When verifying invoice request and invoice messages, these TLVs must be included. Similarly, OfferId construction should included these TLVs as well. Modify the BOLT12 verification tests to cover these TLVs. --- lightning/src/offers/invoice.rs | 11 ++++-- lightning/src/offers/invoice_request.rs | 6 +++- lightning/src/offers/offer.rs | 36 +++++++++++++++++-- lightning/src/offers/refund.rs | 30 ++++++++++++++-- lightning/src/offers/static_invoice.rs | 48 ++++++++++++++++++++++++- 5 files changed, 121 insertions(+), 10 deletions(-) diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index c8be0e377..fcc723474 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -1716,7 +1716,9 @@ mod tests { message_paths: None, }, SignatureTlvStreamRef { signature: Some(&invoice.signature()) }, - ExperimentalOfferTlvStreamRef {}, + ExperimentalOfferTlvStreamRef { + experimental_foo: None, + }, ), ); @@ -1810,7 +1812,9 @@ mod tests { message_paths: None, }, SignatureTlvStreamRef { signature: Some(&invoice.signature()) }, - ExperimentalOfferTlvStreamRef {}, + ExperimentalOfferTlvStreamRef { + experimental_foo: None, + }, ), ); @@ -1904,6 +1908,7 @@ mod tests { let offer = OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, nonce, &secp_ctx) .amount_msats(1000) .path(blinded_path) + .experimental_foo(42) .build().unwrap(); let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap() .build().unwrap() @@ -1925,6 +1930,7 @@ mod tests { let offer = OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, nonce, &secp_ctx) .amount_msats(1000) // Omit the path so that node_id is used for the signing pubkey instead of deriving it + .experimental_foo(42) .build().unwrap(); let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap() .build().unwrap() @@ -1946,6 +1952,7 @@ mod tests { let secp_ctx = Secp256k1::new(); let refund = RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap() + .experimental_foo(42) .build().unwrap(); if let Err(e) = refund diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index 231c582d5..b80d82ce3 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -1434,7 +1434,9 @@ mod tests { paths: None, }, SignatureTlvStreamRef { signature: Some(&invoice_request.signature()) }, - ExperimentalOfferTlvStreamRef {}, + ExperimentalOfferTlvStreamRef { + experimental_foo: None, + }, ), ); @@ -1482,6 +1484,7 @@ mod tests { let offer = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) + .experimental_foo(42) .build().unwrap(); let invoice_request = offer .request_invoice_deriving_metadata(signing_pubkey, &expanded_key, nonce, payment_id) @@ -1565,6 +1568,7 @@ mod tests { let offer = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) + .experimental_foo(42) .build().unwrap(); let invoice_request = offer .request_invoice_deriving_signing_pubkey(&expanded_key, nonce, &secp_ctx, payment_id) diff --git a/lightning/src/offers/offer.rs b/lightning/src/offers/offer.rs index 1c7eac616..3f7e049dc 100644 --- a/lightning/src/offers/offer.rs +++ b/lightning/src/offers/offer.rs @@ -225,6 +225,8 @@ macro_rules! offer_explicit_metadata_builder_methods { ( chains: None, metadata: None, amount: None, description: None, features: OfferFeatures::empty(), absolute_expiry: None, issuer: None, paths: None, supported_quantity: Quantity::One, issuer_signing_pubkey: Some(signing_pubkey), + #[cfg(test)] + experimental_foo: None, }, metadata_strategy: core::marker::PhantomData, secp_ctx: None, @@ -266,6 +268,8 @@ macro_rules! offer_derived_metadata_builder_methods { ($secp_context: ty) => { chains: None, metadata: Some(metadata), amount: None, description: None, features: OfferFeatures::empty(), absolute_expiry: None, issuer: None, paths: None, supported_quantity: Quantity::One, issuer_signing_pubkey: Some(node_id), + #[cfg(test)] + experimental_foo: None, }, metadata_strategy: core::marker::PhantomData, secp_ctx: Some(secp_ctx), @@ -464,6 +468,12 @@ macro_rules! offer_builder_test_methods { ( $return_value } + #[cfg_attr(c_bindings, allow(dead_code))] + pub(super) fn experimental_foo($($self_mut)* $self: $self_type, experimental_foo: u64) -> $return_type { + $self.offer.experimental_foo = Some(experimental_foo); + $return_value + } + #[cfg_attr(c_bindings, allow(dead_code))] pub(super) fn build_unchecked($self: $self_type) -> Offer { $self.build_without_checks() @@ -571,6 +581,8 @@ pub(super) struct OfferContents { paths: Option>, supported_quantity: Quantity, issuer_signing_pubkey: Option, + #[cfg(test)] + experimental_foo: Option, } macro_rules! offer_accessors { ($self: ident, $contents: expr) => { @@ -1010,7 +1022,10 @@ impl OfferContents { issuer_id: self.issuer_signing_pubkey.as_ref(), }; - let experimental_offer = ExperimentalOfferTlvStreamRef {}; + let experimental_offer = ExperimentalOfferTlvStreamRef { + #[cfg(test)] + experimental_foo: self.experimental_foo, + }; (offer, experimental_offer) } @@ -1107,9 +1122,15 @@ 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; +#[cfg(not(test))] tlv_stream!(ExperimentalOfferTlvStream, ExperimentalOfferTlvStreamRef, EXPERIMENTAL_OFFER_TYPES, { }); +#[cfg(test)] +tlv_stream!(ExperimentalOfferTlvStream, ExperimentalOfferTlvStreamRef, EXPERIMENTAL_OFFER_TYPES, { + (1_999_999_999, experimental_foo: (u64, HighZeroBytesDroppedBigSize)), +}); + type FullOfferTlvStream = (OfferTlvStream, ExperimentalOfferTlvStream); type FullOfferTlvStreamRef<'a> = (OfferTlvStreamRef<'a>, ExperimentalOfferTlvStreamRef); @@ -1157,7 +1178,10 @@ impl TryFrom for OfferContents { chains, metadata, currency, amount, description, features, absolute_expiry, paths, issuer, quantity_max, issuer_id, }, - ExperimentalOfferTlvStream {}, + ExperimentalOfferTlvStream { + #[cfg(test)] + experimental_foo, + }, ) = tlv_stream; let metadata = metadata.map(|metadata| Metadata::Bytes(metadata)); @@ -1196,6 +1220,8 @@ impl TryFrom for OfferContents { Ok(OfferContents { chains, metadata, amount, description, features, absolute_expiry, issuer, paths, supported_quantity, issuer_signing_pubkey, + #[cfg(test)] + experimental_foo, }) } } @@ -1274,7 +1300,9 @@ mod tests { quantity_max: None, issuer_id: Some(&pubkey(42)), }, - ExperimentalOfferTlvStreamRef {}, + ExperimentalOfferTlvStreamRef { + experimental_foo: None, + }, ), ); @@ -1354,6 +1382,7 @@ mod tests { use super::OfferWithDerivedMetadataBuilder as OfferBuilder; let offer = OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, nonce, &secp_ctx) .amount_msats(1000) + .experimental_foo(42) .build().unwrap(); assert!(offer.metadata().is_some()); assert_eq!(offer.issuer_signing_pubkey(), Some(node_id)); @@ -1423,6 +1452,7 @@ mod tests { let offer = OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, nonce, &secp_ctx) .amount_msats(1000) .path(blinded_path) + .experimental_foo(42) .build().unwrap(); assert!(offer.metadata().is_none()); assert_ne!(offer.issuer_signing_pubkey(), Some(node_id)); diff --git a/lightning/src/offers/refund.rs b/lightning/src/offers/refund.rs index 8808c7c50..a9f18d1b9 100644 --- a/lightning/src/offers/refund.rs +++ b/lightning/src/offers/refund.rs @@ -176,6 +176,8 @@ macro_rules! refund_explicit_metadata_builder_methods { () => { payer: PayerContents(metadata), description: String::new(), absolute_expiry: None, issuer: None, chain: None, amount_msats, features: InvoiceRequestFeatures::empty(), quantity: None, payer_signing_pubkey: signing_pubkey, payer_note: None, paths: None, + #[cfg(test)] + experimental_foo: None, }, secp_ctx: None, }) @@ -218,6 +220,8 @@ macro_rules! refund_builder_methods { ( payer: PayerContents(metadata), description: String::new(), absolute_expiry: None, issuer: None, chain: None, amount_msats, features: InvoiceRequestFeatures::empty(), quantity: None, payer_signing_pubkey: node_id, payer_note: None, paths: None, + #[cfg(test)] + experimental_foo: None, }, secp_ctx: Some(secp_ctx), }) @@ -358,6 +362,12 @@ macro_rules! refund_builder_test_methods { ( $self.refund.features = features; $return_value } + + #[cfg_attr(c_bindings, allow(dead_code))] + pub(super) fn experimental_foo($($self_mut)* $self: $self_type, experimental_foo: u64) -> $return_type { + $self.refund.experimental_foo = Some(experimental_foo); + $return_value + } } } impl<'a> RefundBuilder<'a, secp256k1::SignOnly> { @@ -437,6 +447,8 @@ pub(super) struct RefundContents { payer_signing_pubkey: PublicKey, payer_note: Option, paths: Option>, + #[cfg(test)] + experimental_foo: Option, } impl Refund { @@ -770,7 +782,10 @@ impl RefundContents { paths: self.paths.as_ref(), }; - let experimental_offer = ExperimentalOfferTlvStreamRef {}; + let experimental_offer = ExperimentalOfferTlvStreamRef { + #[cfg(test)] + experimental_foo: self.experimental_foo, + }; (payer, offer, invoice_request, experimental_offer) } @@ -855,7 +870,10 @@ impl TryFrom for RefundContents { InvoiceRequestTlvStream { chain, amount, features, quantity, payer_id, payer_note, paths }, - _experimental_offer_tlv_stream, + ExperimentalOfferTlvStream { + #[cfg(test)] + experimental_foo, + }, ) = tlv_stream; let payer = match payer_metadata { @@ -916,6 +934,8 @@ impl TryFrom for RefundContents { Ok(RefundContents { payer, description, absolute_expiry, issuer, chain, amount_msats, features, quantity, payer_signing_pubkey, payer_note, paths, + #[cfg(test)] + experimental_foo, }) } } @@ -1021,7 +1041,9 @@ mod tests { payer_note: None, paths: None, }, - ExperimentalOfferTlvStreamRef {}, + ExperimentalOfferTlvStreamRef { + experimental_foo: None, + }, ), ); @@ -1050,6 +1072,7 @@ mod tests { let refund = RefundBuilder ::deriving_signing_pubkey(node_id, &expanded_key, nonce, &secp_ctx, 1000, payment_id) .unwrap() + .experimental_foo(42) .build().unwrap(); assert_eq!(refund.payer_signing_pubkey(), node_id); @@ -1117,6 +1140,7 @@ mod tests { ::deriving_signing_pubkey(node_id, &expanded_key, nonce, &secp_ctx, 1000, payment_id) .unwrap() .path(blinded_path) + .experimental_foo(42) .build().unwrap(); assert_ne!(refund.payer_signing_pubkey(), node_id); diff --git a/lightning/src/offers/static_invoice.rs b/lightning/src/offers/static_invoice.rs index 104588d92..a2ea4032c 100644 --- a/lightning/src/offers/static_invoice.rs +++ b/lightning/src/offers/static_invoice.rs @@ -848,7 +848,7 @@ mod tests { message_paths: Some(&paths), }, SignatureTlvStreamRef { signature: Some(&invoice.signature()) }, - ExperimentalOfferTlvStreamRef {}, + ExperimentalOfferTlvStreamRef { experimental_foo: None }, ) ); @@ -916,6 +916,52 @@ mod tests { } } + #[test] + fn builds_invoice_from_offer_using_derived_key() { + let node_id = recipient_pubkey(); + let now = now(); + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + + let offer = OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, nonce, &secp_ctx) + .path(blinded_path()) + .experimental_foo(42) + .build() + .unwrap(); + + if let Err(e) = StaticInvoiceBuilder::for_offer_using_derived_keys( + &offer, + payment_paths(), + vec![blinded_path()], + now, + &expanded_key, + nonce, + &secp_ctx, + ) + .unwrap() + .build_and_sign(&secp_ctx) + { + panic!("error building invoice: {:?}", e); + } + + let expanded_key = ExpandedKey::new(&KeyMaterial([41; 32])); + if let Err(e) = StaticInvoiceBuilder::for_offer_using_derived_keys( + &offer, + payment_paths(), + vec![blinded_path()], + now, + &expanded_key, + nonce, + &secp_ctx, + ) { + assert_eq!(e, Bolt12SemanticError::InvalidMetadata); + } else { + panic!("expected error") + } + } + #[test] fn fails_build_with_missing_paths() { let node_id = recipient_pubkey(); -- 2.39.5