Skip to content

Commit 0d7ebd6

Browse files
committed
Add parsing tests for experimental invoice TLVs
1 parent 6011150 commit 0d7ebd6

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

@@ -656,6 +658,8 @@ struct InvoiceFields {
656658
fallbacks: Option<Vec<FallbackAddress>>,
657659
features: Bolt12InvoiceFeatures,
658660
signing_pubkey: PublicKey,
661+
#[cfg(test)]
662+
experimental_baz: Option<u64>,
659663
}
660664

661665
macro_rules! invoice_accessors { ($self: ident, $contents: expr) => {
@@ -1239,7 +1243,10 @@ impl InvoiceFields {
12391243
node_id: Some(&self.signing_pubkey),
12401244
message_paths: None,
12411245
},
1242-
ExperimentalInvoiceTlvStreamRef {},
1246+
ExperimentalInvoiceTlvStreamRef {
1247+
#[cfg(test)]
1248+
experimental_baz: self.experimental_baz,
1249+
},
12431250
)
12441251
}
12451252
}
@@ -1316,12 +1323,20 @@ tlv_stream!(InvoiceTlvStream, InvoiceTlvStreamRef<'a>, INVOICE_TYPES, {
13161323
});
13171324

13181325
/// Valid type range for experimental invoice TLV records.
1319-
const EXPERIMENTAL_INVOICE_TYPES: core::ops::RangeFrom<u64> = 3_000_000_000..;
1326+
pub(super) const EXPERIMENTAL_INVOICE_TYPES: core::ops::RangeFrom<u64> = 3_000_000_000..;
13201327

1328+
#[cfg(not(test))]
13211329
tlv_stream!(
13221330
ExperimentalInvoiceTlvStream, ExperimentalInvoiceTlvStreamRef, EXPERIMENTAL_INVOICE_TYPES, {}
13231331
);
13241332

1333+
#[cfg(test)]
1334+
tlv_stream!(
1335+
ExperimentalInvoiceTlvStream, ExperimentalInvoiceTlvStreamRef, EXPERIMENTAL_INVOICE_TYPES, {
1336+
(3_999_999_999, experimental_baz: (u64, HighZeroBytesDroppedBigSize)),
1337+
}
1338+
);
1339+
13251340
pub(super) type BlindedPathIter<'a> = core::iter::Map<
13261341
core::slice::Iter<'a, BlindedPaymentPath>,
13271342
for<'r> fn(&'r BlindedPaymentPath) -> &'r BlindedPath,
@@ -1456,7 +1471,10 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
14561471
},
14571472
experimental_offer_tlv_stream,
14581473
experimental_invoice_request_tlv_stream,
1459-
ExperimentalInvoiceTlvStream {},
1474+
ExperimentalInvoiceTlvStream {
1475+
#[cfg(test)]
1476+
experimental_baz,
1477+
},
14601478
) = tlv_stream;
14611479

14621480
if message_paths.is_some() { return Err(Bolt12SemanticError::UnexpectedPaths) }
@@ -1483,6 +1501,8 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
14831501
let fields = InvoiceFields {
14841502
payment_paths, created_at, relative_expiry, payment_hash, amount_msats, fallbacks,
14851503
features, signing_pubkey,
1504+
#[cfg(test)]
1505+
experimental_baz,
14861506
};
14871507

14881508
check_invoice_signing_pubkey(&fields.signing_pubkey, &offer_tlv_stream)?;
@@ -1553,7 +1573,7 @@ pub(super) fn check_invoice_signing_pubkey(
15531573

15541574
#[cfg(test)]
15551575
mod tests {
1556-
use super::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, ExperimentalInvoiceTlvStreamRef, FallbackAddress, FullInvoiceTlvStreamRef, INVOICE_TYPES, InvoiceTlvStreamRef, SIGNATURE_TAG, UnsignedBolt12Invoice};
1576+
use super::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, EXPERIMENTAL_INVOICE_TYPES, ExperimentalInvoiceTlvStreamRef, FallbackAddress, FullInvoiceTlvStreamRef, INVOICE_TYPES, InvoiceTlvStreamRef, SIGNATURE_TAG, UnsignedBolt12Invoice};
15571577

15581578
use bitcoin::{CompressedPublicKey, WitnessProgram, WitnessVersion};
15591579
use bitcoin::constants::ChainHash;
@@ -1573,7 +1593,7 @@ mod tests {
15731593
use crate::ln::inbound_payment::ExpandedKey;
15741594
use crate::ln::msgs::DecodeError;
15751595
use crate::offers::invoice_request::{ExperimentalInvoiceRequestTlvStreamRef, InvoiceRequestTlvStreamRef};
1576-
use crate::offers::merkle::{SignError, SignatureTlvStreamRef, TaggedHash, self};
1596+
use crate::offers::merkle::{SignError, SignatureTlvStreamRef, TaggedHash, TlvStream, self};
15771597
use crate::offers::nonce::Nonce;
15781598
use crate::offers::offer::{Amount, ExperimentalOfferTlvStreamRef, OfferTlvStreamRef, Quantity};
15791599
use crate::prelude::*;
@@ -1749,7 +1769,9 @@ mod tests {
17491769
ExperimentalInvoiceRequestTlvStreamRef {
17501770
experimental_bar: None,
17511771
},
1752-
ExperimentalInvoiceTlvStreamRef {},
1772+
ExperimentalInvoiceTlvStreamRef {
1773+
experimental_baz: None,
1774+
},
17531775
),
17541776
);
17551777

@@ -1849,7 +1871,9 @@ mod tests {
18491871
ExperimentalInvoiceRequestTlvStreamRef {
18501872
experimental_bar: None,
18511873
},
1852-
ExperimentalInvoiceTlvStreamRef {},
1874+
ExperimentalInvoiceTlvStreamRef {
1875+
experimental_baz: None,
1876+
},
18531877
),
18541878
);
18551879

@@ -2697,6 +2721,123 @@ mod tests {
26972721
}
26982722
}
26992723

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