Skip to content

Commit

Permalink
Add parsing tests for experimental invoice TLVs
Browse files Browse the repository at this point in the history
  • Loading branch information
jkczyz committed Sep 16, 2024
1 parent 575433d commit 1fc4d51
Show file tree
Hide file tree
Showing 5 changed files with 266 additions and 12 deletions.
129 changes: 122 additions & 7 deletions lightning/src/offers/invoice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,8 @@ macro_rules! invoice_builder_methods { (
InvoiceFields {
payment_paths, created_at, relative_expiry: None, payment_hash, amount_msats,
fallbacks: None, features: Bolt12InvoiceFeatures::empty(), signing_pubkey,
#[cfg(test)]
experimental_baz: None,
}
}

Expand Down Expand Up @@ -633,6 +635,8 @@ struct InvoiceFields {
fallbacks: Option<Vec<FallbackAddress>>,
features: Bolt12InvoiceFeatures,
signing_pubkey: PublicKey,
#[cfg(test)]
experimental_baz: Option<u64>,
}

macro_rules! invoice_accessors { ($self: ident, $contents: expr) => {
Expand Down Expand Up @@ -1216,7 +1220,10 @@ impl InvoiceFields {
node_id: Some(&self.signing_pubkey),
message_paths: None,
},
ExperimentalInvoiceTlvStreamRef {},
ExperimentalInvoiceTlvStreamRef {
#[cfg(test)]
experimental_baz: self.experimental_baz,
},
)
}
}
Expand Down Expand Up @@ -1305,12 +1312,20 @@ tlv_stream!(InvoiceTlvStream, InvoiceTlvStreamRef<'a>, INVOICE_TYPES, {
});

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

#[cfg(not(test))]
tlv_stream!(
ExperimentalInvoiceTlvStream, ExperimentalInvoiceTlvStreamRef, EXPERIMENTAL_INVOICE_TYPES, {}
);

#[cfg(test)]
tlv_stream!(
ExperimentalInvoiceTlvStream, ExperimentalInvoiceTlvStreamRef, EXPERIMENTAL_INVOICE_TYPES, {
(3_999_999_999, experimental_baz: (u64, HighZeroBytesDroppedBigSize)),
}
);

pub(super) type BlindedPathIter<'a> = core::iter::Map<
core::slice::Iter<'a, BlindedPaymentPath>,
for<'r> fn(&'r BlindedPaymentPath) -> &'r BlindedPath,
Expand Down Expand Up @@ -1445,7 +1460,10 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
},
experimental_offer_tlv_stream,
experimental_invoice_request_tlv_stream,
ExperimentalInvoiceTlvStream {},
ExperimentalInvoiceTlvStream {
#[cfg(test)]
experimental_baz,
},
) = tlv_stream;

if message_paths.is_some() { return Err(Bolt12SemanticError::UnexpectedPaths) }
Expand All @@ -1472,6 +1490,8 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
let fields = InvoiceFields {
payment_paths, created_at, relative_expiry, payment_hash, amount_msats, fallbacks,
features, signing_pubkey,
#[cfg(test)]
experimental_baz,
};

check_invoice_signing_pubkey(&fields.signing_pubkey, &offer_tlv_stream)?;
Expand Down Expand Up @@ -1542,7 +1562,7 @@ pub(super) fn check_invoice_signing_pubkey(

#[cfg(test)]
mod tests {
use super::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, ExperimentalInvoiceTlvStreamRef, FallbackAddress, FullInvoiceTlvStreamRef, INVOICE_TYPES, InvoiceTlvStreamRef, SIGNATURE_TAG, UnsignedBolt12Invoice};
use super::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, EXPERIMENTAL_INVOICE_TYPES, ExperimentalInvoiceTlvStreamRef, FallbackAddress, FullInvoiceTlvStreamRef, INVOICE_TYPES, InvoiceTlvStreamRef, SIGNATURE_TAG, UnsignedBolt12Invoice};

use bitcoin::{CompressedPublicKey, WitnessProgram, WitnessVersion};
use bitcoin::constants::ChainHash;
Expand All @@ -1562,7 +1582,7 @@ mod tests {
use crate::ln::inbound_payment::ExpandedKey;
use crate::ln::msgs::DecodeError;
use crate::offers::invoice_request::{ExperimentalInvoiceRequestTlvStreamRef, InvoiceRequestTlvStreamRef};
use crate::offers::merkle::{SignError, SignatureTlvStreamRef, TaggedHash, self};
use crate::offers::merkle::{SignError, SignatureTlvStreamRef, TaggedHash, TlvStream, self};
use crate::offers::nonce::Nonce;
use crate::offers::offer::{Amount, ExperimentalOfferTlvStreamRef, OfferTlvStreamRef, Quantity};
use crate::prelude::*;
Expand Down Expand Up @@ -1738,7 +1758,9 @@ mod tests {
ExperimentalInvoiceRequestTlvStreamRef {
experimental_bar: None,
},
ExperimentalInvoiceTlvStreamRef {},
ExperimentalInvoiceTlvStreamRef {
experimental_baz: None,
},
),
);

Expand Down Expand Up @@ -1838,7 +1860,9 @@ mod tests {
ExperimentalInvoiceRequestTlvStreamRef {
experimental_bar: None,
},
ExperimentalInvoiceTlvStreamRef {},
ExperimentalInvoiceTlvStreamRef {
experimental_baz: None,
},
),
);

Expand Down Expand Up @@ -2685,6 +2709,97 @@ mod tests {
}
}

#[test]
fn parses_invoice_with_experimental_tlv_records() {
let secp_ctx = Secp256k1::new();
let keys = Keypair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
let invoice = OfferBuilder::new(keys.public_key())
.amount_msats(1000)
.build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap()
.sign(payer_sign).unwrap()
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
.experimental_baz(42)
.build().unwrap()
.sign(|message: &UnsignedBolt12Invoice|
Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
)
.unwrap();

let mut buffer = Vec::new();
invoice.write(&mut buffer).unwrap();

assert!(Bolt12Invoice::try_from(buffer).is_ok());

const UNKNOWN_ODD_TYPE: u64 = EXPERIMENTAL_INVOICE_TYPES.start + 1;
assert!(UNKNOWN_ODD_TYPE % 2 == 1);

let mut unsigned_invoice = OfferBuilder::new(keys.public_key())
.amount_msats(1000)
.build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap()
.sign(payer_sign).unwrap()
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
.build().unwrap();

BigSize(UNKNOWN_ODD_TYPE).write(&mut unsigned_invoice.experimental_bytes).unwrap();
BigSize(32).write(&mut unsigned_invoice.experimental_bytes).unwrap();
[42u8; 32].write(&mut unsigned_invoice.experimental_bytes).unwrap();

let tlv_stream = TlvStream::new(&unsigned_invoice.bytes)
.chain(TlvStream::new(&unsigned_invoice.experimental_bytes));
unsigned_invoice.tagged_hash = TaggedHash::from_tlv_stream(SIGNATURE_TAG, tlv_stream);

let invoice = unsigned_invoice
.sign(|message: &UnsignedBolt12Invoice|
Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
)
.unwrap();

let mut encoded_invoice = Vec::new();
invoice.write(&mut encoded_invoice).unwrap();

if let Err(e) = Bolt12Invoice::try_from(encoded_invoice) {
panic!("error parsing invoice: {:?}", e);
}

const UNKNOWN_EVEN_TYPE: u64 = EXPERIMENTAL_INVOICE_TYPES.start;
assert!(UNKNOWN_EVEN_TYPE % 2 == 0);

let mut unsigned_invoice = OfferBuilder::new(keys.public_key())
.amount_msats(1000)
.build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap()
.sign(payer_sign).unwrap()
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
.build().unwrap();

BigSize(UNKNOWN_EVEN_TYPE).write(&mut unsigned_invoice.experimental_bytes).unwrap();
BigSize(32).write(&mut unsigned_invoice.experimental_bytes).unwrap();
[42u8; 32].write(&mut unsigned_invoice.experimental_bytes).unwrap();

let tlv_stream = TlvStream::new(&unsigned_invoice.bytes)
.chain(TlvStream::new(&unsigned_invoice.experimental_bytes));
unsigned_invoice.tagged_hash = TaggedHash::from_tlv_stream(SIGNATURE_TAG, tlv_stream);

let invoice = unsigned_invoice
.sign(|message: &UnsignedBolt12Invoice|
Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
)
.unwrap();

let mut encoded_invoice = Vec::new();
invoice.write(&mut encoded_invoice).unwrap();

match Bolt12Invoice::try_from(encoded_invoice) {
Ok(_) => panic!("expected error"),
Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::UnknownRequiredFeature)),
}
}

#[test]
fn fails_parsing_invoice_with_out_of_range_tlv_records() {
let invoice = OfferBuilder::new(recipient_pubkey())
Expand Down
5 changes: 5 additions & 0 deletions lightning/src/offers/invoice_macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ macro_rules! invoice_builder_methods_test { (
$return_value
}

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

macro_rules! invoice_accessors_common { ($self: ident, $contents: expr, $invoice_type: ty) => {
Expand Down
2 changes: 2 additions & 0 deletions lightning/src/offers/invoice_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1525,6 +1525,7 @@ mod tests {

let invoice = invoice_request.respond_with_no_std(payment_paths(), payment_hash(), now())
.unwrap()
.experimental_baz(42)
.build().unwrap()
.sign(recipient_sign).unwrap();
match invoice.verify_using_metadata(&expanded_key, &secp_ctx) {
Expand Down Expand Up @@ -1617,6 +1618,7 @@ mod tests {

let invoice = invoice_request.respond_with_no_std(payment_paths(), payment_hash(), now())
.unwrap()
.experimental_baz(42)
.build().unwrap()
.sign(recipient_sign).unwrap();
assert!(invoice.verify_using_metadata(&expanded_key, &secp_ctx).is_err());
Expand Down
2 changes: 2 additions & 0 deletions lightning/src/offers/refund.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1110,6 +1110,7 @@ mod tests {
let invoice = refund
.respond_with_no_std(payment_paths(), payment_hash(), recipient_pubkey(), now())
.unwrap()
.experimental_baz(42)
.build().unwrap()
.sign(recipient_sign).unwrap();
match invoice.verify_using_metadata(&expanded_key, &secp_ctx) {
Expand Down Expand Up @@ -1178,6 +1179,7 @@ mod tests {
let invoice = refund
.respond_with_no_std(payment_paths(), payment_hash(), recipient_pubkey(), now())
.unwrap()
.experimental_baz(42)
.build().unwrap()
.sign(recipient_sign).unwrap();
assert!(invoice.verify_using_metadata(&expanded_key, &secp_ctx).is_err());
Expand Down
Loading

0 comments on commit 1fc4d51

Please sign in to comment.