]> git.bitcoin.ninja Git - rust-lightning/commitdiff
Test verification with experimental offer TLVs
authorJeffrey Czyz <jkczyz@gmail.com>
Tue, 6 Aug 2024 21:21:32 +0000 (16:21 -0500)
committerJeffrey Czyz <jkczyz@gmail.com>
Tue, 5 Nov 2024 00:00:23 +0000 (18:00 -0600)
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
lightning/src/offers/invoice_request.rs
lightning/src/offers/offer.rs
lightning/src/offers/refund.rs
lightning/src/offers/static_invoice.rs

index c8be0e3771e89c03f585e7ad43dedbf5b4f2279c..fcc723474cf3d51747f0d4e3b125eef6bbd39521 100644 (file)
@@ -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
index 231c582d599c506ccb6795479d965cdcd935902e..b80d82ce38257b309263a58efe07025510ea4e91 100644 (file)
@@ -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)
index 1c7eac6167e63f302e917c8dd8d5e8c20b8c9065..3f7e049dc9cecb4ef2696f848a89e1f8284d887a 100644 (file)
@@ -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<Vec<BlindedMessagePath>>,
        supported_quantity: Quantity,
        issuer_signing_pubkey: Option<PublicKey>,
+       #[cfg(test)]
+       experimental_foo: Option<u64>,
 }
 
 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<u64> = 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<FullOfferTlvStream> 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<FullOfferTlvStream> 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));
index 8808c7c5002e5c3243907dbe163567252774bef8..a9f18d1b94de730ff70e65d852837f3a49190304 100644 (file)
@@ -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<String>,
        paths: Option<Vec<BlindedMessagePath>>,
+       #[cfg(test)]
+       experimental_foo: Option<u64>,
 }
 
 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<RefundTlvStream> 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<RefundTlvStream> 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);
 
index 104588d921759a420a0fd211067259fcfd01de25..a2ea4032c643550fa5ca9a92300bd18e5f6692bb 100644 (file)
@@ -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();