Skip to content

Commit 7668240

Browse files
committed
Add parsing tests for experimental invoice TLVs
1 parent 8e279f7 commit 7668240

File tree

5 files changed

+348
-12
lines changed

5 files changed

+348
-12
lines changed

lightning/src/offers/invoice.rs

Lines changed: 160 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,8 @@ macro_rules! invoice_builder_methods { (
363363
InvoiceFields {
364364
payment_paths, created_at, relative_expiry: None, payment_hash, amount_msats,
365365
fallbacks: None, features: Bolt12InvoiceFeatures::empty(), signing_pubkey,
366+
#[cfg(test)]
367+
experimental_baz: None,
366368
}
367369
}
368370

@@ -666,6 +668,8 @@ struct InvoiceFields {
666668
fallbacks: Option<Vec<FallbackAddress>>,
667669
features: Bolt12InvoiceFeatures,
668670
signing_pubkey: PublicKey,
671+
#[cfg(test)]
672+
experimental_baz: Option<u64>,
669673
}
670674

671675
macro_rules! invoice_accessors { ($self: ident, $contents: expr) => {
@@ -1256,7 +1260,10 @@ impl InvoiceFields {
12561260
node_id: Some(&self.signing_pubkey),
12571261
message_paths: None,
12581262
},
1259-
ExperimentalInvoiceTlvStreamRef {},
1263+
ExperimentalInvoiceTlvStreamRef {
1264+
#[cfg(test)]
1265+
experimental_baz: self.experimental_baz,
1266+
},
12601267
)
12611268
}
12621269
}
@@ -1333,12 +1340,20 @@ tlv_stream!(InvoiceTlvStream, InvoiceTlvStreamRef<'a>, INVOICE_TYPES, {
13331340
});
13341341

13351342
/// Valid type range for experimental invoice TLV records.
1336-
const EXPERIMENTAL_INVOICE_TYPES: core::ops::RangeFrom<u64> = 3_000_000_000..;
1343+
pub(super) const EXPERIMENTAL_INVOICE_TYPES: core::ops::RangeFrom<u64> = 3_000_000_000..;
13371344

1345+
#[cfg(not(test))]
13381346
tlv_stream!(
13391347
ExperimentalInvoiceTlvStream, ExperimentalInvoiceTlvStreamRef, EXPERIMENTAL_INVOICE_TYPES, {}
13401348
);
13411349

1350+
#[cfg(test)]
1351+
tlv_stream!(
1352+
ExperimentalInvoiceTlvStream, ExperimentalInvoiceTlvStreamRef, EXPERIMENTAL_INVOICE_TYPES, {
1353+
(3_999_999_999, experimental_baz: (u64, HighZeroBytesDroppedBigSize)),
1354+
}
1355+
);
1356+
13421357
pub(super) type BlindedPathIter<'a> = core::iter::Map<
13431358
core::slice::Iter<'a, BlindedPaymentPath>,
13441359
for<'r> fn(&'r BlindedPaymentPath) -> &'r BlindedPath,
@@ -1473,7 +1488,10 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
14731488
},
14741489
experimental_offer_tlv_stream,
14751490
experimental_invoice_request_tlv_stream,
1476-
ExperimentalInvoiceTlvStream {},
1491+
ExperimentalInvoiceTlvStream {
1492+
#[cfg(test)]
1493+
experimental_baz,
1494+
},
14771495
) = tlv_stream;
14781496

14791497
if message_paths.is_some() { return Err(Bolt12SemanticError::UnexpectedPaths) }
@@ -1500,6 +1518,8 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
15001518
let fields = InvoiceFields {
15011519
payment_paths, created_at, relative_expiry, payment_hash, amount_msats, fallbacks,
15021520
features, signing_pubkey,
1521+
#[cfg(test)]
1522+
experimental_baz,
15031523
};
15041524

15051525
check_invoice_signing_pubkey(&fields.signing_pubkey, &offer_tlv_stream)?;
@@ -1570,7 +1590,7 @@ pub(super) fn check_invoice_signing_pubkey(
15701590

15711591
#[cfg(test)]
15721592
mod tests {
1573-
use super::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, ExperimentalInvoiceTlvStreamRef, FallbackAddress, FullInvoiceTlvStreamRef, INVOICE_TYPES, InvoiceTlvStreamRef, SIGNATURE_TAG, UnsignedBolt12Invoice};
1593+
use super::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, EXPERIMENTAL_INVOICE_TYPES, ExperimentalInvoiceTlvStreamRef, FallbackAddress, FullInvoiceTlvStreamRef, INVOICE_TYPES, InvoiceTlvStreamRef, SIGNATURE_TAG, UnsignedBolt12Invoice};
15741594

15751595
use bitcoin::{CompressedPublicKey, WitnessProgram, WitnessVersion};
15761596
use bitcoin::constants::ChainHash;
@@ -1590,7 +1610,7 @@ mod tests {
15901610
use crate::ln::inbound_payment::ExpandedKey;
15911611
use crate::ln::msgs::DecodeError;
15921612
use crate::offers::invoice_request::{ExperimentalInvoiceRequestTlvStreamRef, InvoiceRequestTlvStreamRef};
1593-
use crate::offers::merkle::{SignError, SignatureTlvStreamRef, TaggedHash, self};
1613+
use crate::offers::merkle::{SignError, SignatureTlvStreamRef, TaggedHash, TlvStream, self};
15941614
use crate::offers::nonce::Nonce;
15951615
use crate::offers::offer::{Amount, ExperimentalOfferTlvStreamRef, OfferTlvStreamRef, Quantity};
15961616
use crate::prelude::*;
@@ -1766,7 +1786,9 @@ mod tests {
17661786
ExperimentalInvoiceRequestTlvStreamRef {
17671787
experimental_bar: None,
17681788
},
1769-
ExperimentalInvoiceTlvStreamRef {},
1789+
ExperimentalInvoiceTlvStreamRef {
1790+
experimental_baz: None,
1791+
},
17701792
),
17711793
);
17721794

@@ -1866,7 +1888,9 @@ mod tests {
18661888
ExperimentalInvoiceRequestTlvStreamRef {
18671889
experimental_bar: None,
18681890
},
1869-
ExperimentalInvoiceTlvStreamRef {},
1891+
ExperimentalInvoiceTlvStreamRef {
1892+
experimental_baz: None,
1893+
},
18701894
),
18711895
);
18721896

@@ -2724,6 +2748,135 @@ mod tests {
27242748
}
27252749
}
27262750

2751+
#[test]
2752+
fn parses_invoice_with_experimental_tlv_records() {
2753+
let secp_ctx = Secp256k1::new();
2754+
let keys = Keypair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
2755+
let invoice = OfferBuilder::new(keys.public_key())
2756+
.amount_msats(1000)
2757+
.build().unwrap()
2758+
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
2759+
.build().unwrap()
2760+
.sign(payer_sign).unwrap()
2761+
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
2762+
.experimental_baz(42)
2763+
.build().unwrap()
2764+
.sign(|message: &UnsignedBolt12Invoice|
2765+
Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
2766+
)
2767+
.unwrap();
2768+
2769+
let mut encoded_invoice = Vec::new();
2770+
invoice.write(&mut encoded_invoice).unwrap();
2771+
2772+
assert!(Bolt12Invoice::try_from(encoded_invoice).is_ok());
2773+
2774+
const UNKNOWN_ODD_TYPE: u64 = EXPERIMENTAL_INVOICE_TYPES.start + 1;
2775+
assert!(UNKNOWN_ODD_TYPE % 2 == 1);
2776+
2777+
let mut unsigned_invoice = OfferBuilder::new(keys.public_key())
2778+
.amount_msats(1000)
2779+
.build().unwrap()
2780+
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
2781+
.build().unwrap()
2782+
.sign(payer_sign).unwrap()
2783+
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
2784+
.build().unwrap();
2785+
2786+
let mut unknown_bytes = Vec::new();
2787+
BigSize(UNKNOWN_ODD_TYPE).write(&mut unknown_bytes).unwrap();
2788+
BigSize(32).write(&mut unknown_bytes).unwrap();
2789+
[42u8; 32].write(&mut unknown_bytes).unwrap();
2790+
2791+
unsigned_invoice.bytes.reserve_exact(
2792+
unsigned_invoice.bytes.capacity() - unsigned_invoice.bytes.len() + unknown_bytes.len(),
2793+
);
2794+
unsigned_invoice.experimental_bytes.extend_from_slice(&unknown_bytes);
2795+
2796+
let tlv_stream = TlvStream::new(&unsigned_invoice.bytes)
2797+
.chain(TlvStream::new(&unsigned_invoice.experimental_bytes));
2798+
unsigned_invoice.tagged_hash = TaggedHash::from_tlv_stream(SIGNATURE_TAG, tlv_stream);
2799+
2800+
let invoice = unsigned_invoice
2801+
.sign(|message: &UnsignedBolt12Invoice|
2802+
Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
2803+
)
2804+
.unwrap();
2805+
2806+
let mut encoded_invoice = Vec::new();
2807+
invoice.write(&mut encoded_invoice).unwrap();
2808+
2809+
match Bolt12Invoice::try_from(encoded_invoice.clone()) {
2810+
Ok(invoice) => assert_eq!(invoice.bytes, encoded_invoice),
2811+
Err(e) => panic!("error parsing invoice: {:?}", e),
2812+
}
2813+
2814+
const UNKNOWN_EVEN_TYPE: u64 = EXPERIMENTAL_INVOICE_TYPES.start;
2815+
assert!(UNKNOWN_EVEN_TYPE % 2 == 0);
2816+
2817+
let mut unsigned_invoice = OfferBuilder::new(keys.public_key())
2818+
.amount_msats(1000)
2819+
.build().unwrap()
2820+
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
2821+
.build().unwrap()
2822+
.sign(payer_sign).unwrap()
2823+
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
2824+
.build().unwrap();
2825+
2826+
let mut unknown_bytes = Vec::new();
2827+
BigSize(UNKNOWN_EVEN_TYPE).write(&mut unknown_bytes).unwrap();
2828+
BigSize(32).write(&mut unknown_bytes).unwrap();
2829+
[42u8; 32].write(&mut unknown_bytes).unwrap();
2830+
2831+
unsigned_invoice.bytes.reserve_exact(
2832+
unsigned_invoice.bytes.capacity() - unsigned_invoice.bytes.len() + unknown_bytes.len(),
2833+
);
2834+
unsigned_invoice.experimental_bytes.extend_from_slice(&unknown_bytes);
2835+
2836+
let tlv_stream = TlvStream::new(&unsigned_invoice.bytes)
2837+
.chain(TlvStream::new(&unsigned_invoice.experimental_bytes));
2838+
unsigned_invoice.tagged_hash = TaggedHash::from_tlv_stream(SIGNATURE_TAG, tlv_stream);
2839+
2840+
let invoice = unsigned_invoice
2841+
.sign(|message: &UnsignedBolt12Invoice|
2842+
Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
2843+
)
2844+
.unwrap();
2845+
2846+
let mut encoded_invoice = Vec::new();
2847+
invoice.write(&mut encoded_invoice).unwrap();
2848+
2849+
match Bolt12Invoice::try_from(encoded_invoice) {
2850+
Ok(_) => panic!("expected error"),
2851+
Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::UnknownRequiredFeature)),
2852+
}
2853+
2854+
let invoice = OfferBuilder::new(keys.public_key())
2855+
.amount_msats(1000)
2856+
.build().unwrap()
2857+
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
2858+
.build().unwrap()
2859+
.sign(payer_sign).unwrap()
2860+
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
2861+
.build().unwrap()
2862+
.sign(|message: &UnsignedBolt12Invoice|
2863+
Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
2864+
)
2865+
.unwrap();
2866+
2867+
let mut encoded_invoice = Vec::new();
2868+
invoice.write(&mut encoded_invoice).unwrap();
2869+
2870+
BigSize(UNKNOWN_ODD_TYPE).write(&mut encoded_invoice).unwrap();
2871+
BigSize(32).write(&mut encoded_invoice).unwrap();
2872+
[42u8; 32].write(&mut encoded_invoice).unwrap();
2873+
2874+
match Bolt12Invoice::try_from(encoded_invoice) {
2875+
Ok(_) => panic!("expected error"),
2876+
Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSignature(secp256k1::Error::IncorrectSignature)),
2877+
}
2878+
}
2879+
27272880
#[test]
27282881
fn fails_parsing_invoice_with_out_of_range_tlv_records() {
27292882
let invoice = OfferBuilder::new(recipient_pubkey())

lightning/src/offers/invoice_macros.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,11 @@ macro_rules! invoice_builder_methods_test { (
9595
$return_value
9696
}
9797

98+
#[cfg_attr(c_bindings, allow(dead_code))]
99+
pub(super) fn experimental_baz($($self_mut)* $self: $self_type, experimental_baz: u64) -> $return_type {
100+
$invoice_fields.experimental_baz = Some(experimental_baz);
101+
$return_value
102+
}
98103
} }
99104

100105
macro_rules! invoice_accessors_common { ($self: ident, $contents: expr, $invoice_type: ty) => {

lightning/src/offers/invoice_request.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1551,6 +1551,7 @@ mod tests {
15511551

15521552
let invoice = invoice_request.respond_with_no_std(payment_paths(), payment_hash(), now())
15531553
.unwrap()
1554+
.experimental_baz(42)
15541555
.build().unwrap()
15551556
.sign(recipient_sign).unwrap();
15561557
match invoice.verify_using_metadata(&expanded_key, &secp_ctx) {
@@ -1643,6 +1644,7 @@ mod tests {
16431644

16441645
let invoice = invoice_request.respond_with_no_std(payment_paths(), payment_hash(), now())
16451646
.unwrap()
1647+
.experimental_baz(42)
16461648
.build().unwrap()
16471649
.sign(recipient_sign).unwrap();
16481650
assert!(invoice.verify_using_metadata(&expanded_key, &secp_ctx).is_err());

lightning/src/offers/refund.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1110,6 +1110,7 @@ mod tests {
11101110
let invoice = refund
11111111
.respond_with_no_std(payment_paths(), payment_hash(), recipient_pubkey(), now())
11121112
.unwrap()
1113+
.experimental_baz(42)
11131114
.build().unwrap()
11141115
.sign(recipient_sign).unwrap();
11151116
match invoice.verify_using_metadata(&expanded_key, &secp_ctx) {
@@ -1178,6 +1179,7 @@ mod tests {
11781179
let invoice = refund
11791180
.respond_with_no_std(payment_paths(), payment_hash(), recipient_pubkey(), now())
11801181
.unwrap()
1182+
.experimental_baz(42)
11811183
.build().unwrap()
11821184
.sign(recipient_sign).unwrap();
11831185
assert!(invoice.verify_using_metadata(&expanded_key, &secp_ctx).is_err());

0 commit comments

Comments
 (0)