Skip to content

Commit 117c11c

Browse files
committed
Add parsing tests for experimental invoice TLVs
1 parent 48073c5 commit 117c11c

File tree

5 files changed

+324
-12
lines changed

5 files changed

+324
-12
lines changed

lightning/src/offers/invoice.rs

Lines changed: 148 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

@@ -657,6 +659,8 @@ struct InvoiceFields {
657659
fallbacks: Option<Vec<FallbackAddress>>,
658660
features: Bolt12InvoiceFeatures,
659661
signing_pubkey: PublicKey,
662+
#[cfg(test)]
663+
experimental_baz: Option<u64>,
660664
}
661665

662666
macro_rules! invoice_accessors { ($self: ident, $contents: expr) => {
@@ -1247,7 +1251,10 @@ impl InvoiceFields {
12471251
node_id: Some(&self.signing_pubkey),
12481252
message_paths: None,
12491253
},
1250-
ExperimentalInvoiceTlvStreamRef {},
1254+
ExperimentalInvoiceTlvStreamRef {
1255+
#[cfg(test)]
1256+
experimental_baz: self.experimental_baz,
1257+
},
12511258
)
12521259
}
12531260
}
@@ -1324,12 +1331,20 @@ tlv_stream!(InvoiceTlvStream, InvoiceTlvStreamRef<'a>, INVOICE_TYPES, {
13241331
});
13251332

13261333
/// Valid type range for experimental invoice TLV records.
1327-
const EXPERIMENTAL_INVOICE_TYPES: core::ops::RangeFrom<u64> = 3_000_000_000..;
1334+
pub(super) const EXPERIMENTAL_INVOICE_TYPES: core::ops::RangeFrom<u64> = 3_000_000_000..;
13281335

1336+
#[cfg(not(test))]
13291337
tlv_stream!(
13301338
ExperimentalInvoiceTlvStream, ExperimentalInvoiceTlvStreamRef, EXPERIMENTAL_INVOICE_TYPES, {}
13311339
);
13321340

1341+
#[cfg(test)]
1342+
tlv_stream!(
1343+
ExperimentalInvoiceTlvStream, ExperimentalInvoiceTlvStreamRef, EXPERIMENTAL_INVOICE_TYPES, {
1344+
(3_999_999_999, experimental_baz: (u64, HighZeroBytesDroppedBigSize)),
1345+
}
1346+
);
1347+
13331348
pub(super) type BlindedPathIter<'a> = core::iter::Map<
13341349
core::slice::Iter<'a, BlindedPaymentPath>,
13351350
for<'r> fn(&'r BlindedPaymentPath) -> &'r BlindedPath,
@@ -1464,7 +1479,10 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
14641479
},
14651480
experimental_offer_tlv_stream,
14661481
experimental_invoice_request_tlv_stream,
1467-
ExperimentalInvoiceTlvStream {},
1482+
ExperimentalInvoiceTlvStream {
1483+
#[cfg(test)]
1484+
experimental_baz,
1485+
},
14681486
) = tlv_stream;
14691487

14701488
if message_paths.is_some() { return Err(Bolt12SemanticError::UnexpectedPaths) }
@@ -1491,6 +1509,8 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
14911509
let fields = InvoiceFields {
14921510
payment_paths, created_at, relative_expiry, payment_hash, amount_msats, fallbacks,
14931511
features, signing_pubkey,
1512+
#[cfg(test)]
1513+
experimental_baz,
14941514
};
14951515

14961516
check_invoice_signing_pubkey(&fields.signing_pubkey, &offer_tlv_stream)?;
@@ -1561,7 +1581,7 @@ pub(super) fn check_invoice_signing_pubkey(
15611581

15621582
#[cfg(test)]
15631583
mod tests {
1564-
use super::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, ExperimentalInvoiceTlvStreamRef, FallbackAddress, FullInvoiceTlvStreamRef, INVOICE_TYPES, InvoiceTlvStreamRef, SIGNATURE_TAG, UnsignedBolt12Invoice};
1584+
use super::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, EXPERIMENTAL_INVOICE_TYPES, ExperimentalInvoiceTlvStreamRef, FallbackAddress, FullInvoiceTlvStreamRef, INVOICE_TYPES, InvoiceTlvStreamRef, SIGNATURE_TAG, UnsignedBolt12Invoice};
15651585

15661586
use bitcoin::{CompressedPublicKey, WitnessProgram, WitnessVersion};
15671587
use bitcoin::constants::ChainHash;
@@ -1581,7 +1601,7 @@ mod tests {
15811601
use crate::ln::inbound_payment::ExpandedKey;
15821602
use crate::ln::msgs::DecodeError;
15831603
use crate::offers::invoice_request::{ExperimentalInvoiceRequestTlvStreamRef, InvoiceRequestTlvStreamRef};
1584-
use crate::offers::merkle::{SignError, SignatureTlvStreamRef, TaggedHash, self};
1604+
use crate::offers::merkle::{SignError, SignatureTlvStreamRef, TaggedHash, TlvStream, self};
15851605
use crate::offers::nonce::Nonce;
15861606
use crate::offers::offer::{Amount, ExperimentalOfferTlvStreamRef, OfferTlvStreamRef, Quantity};
15871607
use crate::prelude::*;
@@ -1757,7 +1777,9 @@ mod tests {
17571777
ExperimentalInvoiceRequestTlvStreamRef {
17581778
experimental_bar: None,
17591779
},
1760-
ExperimentalInvoiceTlvStreamRef {},
1780+
ExperimentalInvoiceTlvStreamRef {
1781+
experimental_baz: None,
1782+
},
17611783
),
17621784
);
17631785

@@ -1857,7 +1879,9 @@ mod tests {
18571879
ExperimentalInvoiceRequestTlvStreamRef {
18581880
experimental_bar: None,
18591881
},
1860-
ExperimentalInvoiceTlvStreamRef {},
1882+
ExperimentalInvoiceTlvStreamRef {
1883+
experimental_baz: None,
1884+
},
18611885
),
18621886
);
18631887

@@ -2705,6 +2729,123 @@ mod tests {
27052729
}
27062730
}
27072731

2732+
#[test]
2733+
fn parses_invoice_with_experimental_tlv_records() {
2734+
let secp_ctx = Secp256k1::new();
2735+
let keys = Keypair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
2736+
let invoice = OfferBuilder::new(keys.public_key())
2737+
.amount_msats(1000)
2738+
.build().unwrap()
2739+
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
2740+
.build().unwrap()
2741+
.sign(payer_sign).unwrap()
2742+
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
2743+
.experimental_baz(42)
2744+
.build().unwrap()
2745+
.sign(|message: &UnsignedBolt12Invoice|
2746+
Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
2747+
)
2748+
.unwrap();
2749+
2750+
let mut encoded_invoice = Vec::new();
2751+
invoice.write(&mut encoded_invoice).unwrap();
2752+
2753+
assert!(Bolt12Invoice::try_from(encoded_invoice).is_ok());
2754+
2755+
const UNKNOWN_ODD_TYPE: u64 = EXPERIMENTAL_INVOICE_TYPES.start + 1;
2756+
assert!(UNKNOWN_ODD_TYPE % 2 == 1);
2757+
2758+
let mut unsigned_invoice = OfferBuilder::new(keys.public_key())
2759+
.amount_msats(1000)
2760+
.build().unwrap()
2761+
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
2762+
.build().unwrap()
2763+
.sign(payer_sign).unwrap()
2764+
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
2765+
.build().unwrap();
2766+
2767+
BigSize(UNKNOWN_ODD_TYPE).write(&mut unsigned_invoice.experimental_bytes).unwrap();
2768+
BigSize(32).write(&mut unsigned_invoice.experimental_bytes).unwrap();
2769+
[42u8; 32].write(&mut unsigned_invoice.experimental_bytes).unwrap();
2770+
2771+
let tlv_stream = TlvStream::new(&unsigned_invoice.bytes)
2772+
.chain(TlvStream::new(&unsigned_invoice.experimental_bytes));
2773+
unsigned_invoice.tagged_hash = TaggedHash::from_tlv_stream(SIGNATURE_TAG, tlv_stream);
2774+
2775+
let invoice = unsigned_invoice
2776+
.sign(|message: &UnsignedBolt12Invoice|
2777+
Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
2778+
)
2779+
.unwrap();
2780+
2781+
let mut encoded_invoice = Vec::new();
2782+
invoice.write(&mut encoded_invoice).unwrap();
2783+
2784+
match Bolt12Invoice::try_from(encoded_invoice.clone()) {
2785+
Ok(invoice) => assert_eq!(invoice.bytes, encoded_invoice),
2786+
Err(e) => panic!("error parsing invoice: {:?}", e),
2787+
}
2788+
2789+
const UNKNOWN_EVEN_TYPE: u64 = EXPERIMENTAL_INVOICE_TYPES.start;
2790+
assert!(UNKNOWN_EVEN_TYPE % 2 == 0);
2791+
2792+
let mut unsigned_invoice = OfferBuilder::new(keys.public_key())
2793+
.amount_msats(1000)
2794+
.build().unwrap()
2795+
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
2796+
.build().unwrap()
2797+
.sign(payer_sign).unwrap()
2798+
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
2799+
.build().unwrap();
2800+
2801+
BigSize(UNKNOWN_EVEN_TYPE).write(&mut unsigned_invoice.experimental_bytes).unwrap();
2802+
BigSize(32).write(&mut unsigned_invoice.experimental_bytes).unwrap();
2803+
[42u8; 32].write(&mut unsigned_invoice.experimental_bytes).unwrap();
2804+
2805+
let tlv_stream = TlvStream::new(&unsigned_invoice.bytes)
2806+
.chain(TlvStream::new(&unsigned_invoice.experimental_bytes));
2807+
unsigned_invoice.tagged_hash = TaggedHash::from_tlv_stream(SIGNATURE_TAG, tlv_stream);
2808+
2809+
let invoice = unsigned_invoice
2810+
.sign(|message: &UnsignedBolt12Invoice|
2811+
Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
2812+
)
2813+
.unwrap();
2814+
2815+
let mut encoded_invoice = Vec::new();
2816+
invoice.write(&mut encoded_invoice).unwrap();
2817+
2818+
match Bolt12Invoice::try_from(encoded_invoice) {
2819+
Ok(_) => panic!("expected error"),
2820+
Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::UnknownRequiredFeature)),
2821+
}
2822+
2823+
let invoice = OfferBuilder::new(keys.public_key())
2824+
.amount_msats(1000)
2825+
.build().unwrap()
2826+
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
2827+
.build().unwrap()
2828+
.sign(payer_sign).unwrap()
2829+
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
2830+
.build().unwrap()
2831+
.sign(|message: &UnsignedBolt12Invoice|
2832+
Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
2833+
)
2834+
.unwrap();
2835+
2836+
let mut encoded_invoice = Vec::new();
2837+
invoice.write(&mut encoded_invoice).unwrap();
2838+
2839+
BigSize(UNKNOWN_ODD_TYPE).write(&mut encoded_invoice).unwrap();
2840+
BigSize(32).write(&mut encoded_invoice).unwrap();
2841+
[42u8; 32].write(&mut encoded_invoice).unwrap();
2842+
2843+
match Bolt12Invoice::try_from(encoded_invoice) {
2844+
Ok(_) => panic!("expected error"),
2845+
Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSignature(secp256k1::Error::IncorrectSignature)),
2846+
}
2847+
}
2848+
27082849
#[test]
27092850
fn fails_parsing_invoice_with_out_of_range_tlv_records() {
27102851
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
@@ -1544,6 +1544,7 @@ mod tests {
15441544

15451545
let invoice = invoice_request.respond_with_no_std(payment_paths(), payment_hash(), now())
15461546
.unwrap()
1547+
.experimental_baz(42)
15471548
.build().unwrap()
15481549
.sign(recipient_sign).unwrap();
15491550
match invoice.verify_using_metadata(&expanded_key, &secp_ctx) {
@@ -1636,6 +1637,7 @@ mod tests {
16361637

16371638
let invoice = invoice_request.respond_with_no_std(payment_paths(), payment_hash(), now())
16381639
.unwrap()
1640+
.experimental_baz(42)
16391641
.build().unwrap()
16401642
.sign(recipient_sign).unwrap();
16411643
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)