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 Aug 14, 2024
1 parent 03e4a96 commit 1d15a6f
Show file tree
Hide file tree
Showing 5 changed files with 282 additions and 12 deletions.
136 changes: 129 additions & 7 deletions lightning/src/offers/invoice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ use crate::ln::features::{BlindedHopFeatures, Bolt12InvoiceFeatures, InvoiceRequ
use crate::ln::inbound_payment::{ExpandedKey, IV_LEN};
use crate::ln::msgs::DecodeError;
use crate::offers::invoice_macros::{invoice_accessors_common, invoice_builder_methods_common};
#[cfg(test)]
use crate::offers::invoice_macros::invoice_builder_test_methods;
use crate::offers::invoice_request::{EXPERIMENTAL_INVOICE_REQUEST_TYPES, ExperimentalInvoiceRequestTlvStream, ExperimentalInvoiceRequestTlvStreamRef, INVOICE_REQUEST_PAYER_ID_TYPE, INVOICE_REQUEST_TYPES, IV_BYTES as INVOICE_REQUEST_IV_BYTES, InvoiceRequest, InvoiceRequestContents, InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef};
use crate::offers::merkle::{SignError, SignFn, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, TlvStream, self};
use crate::offers::nonce::Nonce;
Expand Down Expand Up @@ -359,6 +361,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 All @@ -385,6 +389,9 @@ impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> {
impl<'a, S: SigningPubkeyStrategy> InvoiceBuilder<'a, S> {
invoice_builder_methods!(self, Self, Self, self, S, mut);
invoice_builder_methods_common!(self, Self, self.invoice.fields_mut(), Self, self, S, Bolt12Invoice, mut);

#[cfg(test)]
invoice_builder_test_methods!(self, Self, self.invoice.fields_mut(), Self, self, mut);
}

#[cfg(all(c_bindings, not(test)))]
Expand All @@ -399,6 +406,7 @@ impl<'a> InvoiceWithExplicitSigningPubkeyBuilder<'a> {
invoice_explicit_signing_pubkey_builder_methods!(self, &mut Self);
invoice_builder_methods!(self, &mut Self, &mut Self, self, ExplicitSigningPubkey);
invoice_builder_methods_common!(self, &mut Self, self.invoice.fields_mut(), &mut Self, self, ExplicitSigningPubkey, Bolt12Invoice);
invoice_builder_test_methods!(self, &mut Self, self.invoice.fields_mut(), &mut Self, self);
}

#[cfg(all(c_bindings, not(test)))]
Expand All @@ -413,6 +421,7 @@ impl<'a> InvoiceWithDerivedSigningPubkeyBuilder<'a> {
invoice_derived_signing_pubkey_builder_methods!(self, &mut Self);
invoice_builder_methods!(self, &mut Self, &mut Self, self, DerivedSigningPubkey);
invoice_builder_methods_common!(self, &mut Self, self.invoice.fields_mut(), &mut Self, self, DerivedSigningPubkey, Bolt12Invoice);
invoice_builder_test_methods!(self, &mut Self, self.invoice.fields_mut(), &mut Self, self);
}

#[cfg(c_bindings)]
Expand Down Expand Up @@ -624,6 +633,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 @@ -1208,7 +1219,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 @@ -1297,12 +1311,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, (BlindedPayInfo, BlindedPath)>,
for<'r> fn(&'r (BlindedPayInfo, BlindedPath)) -> &'r BlindedPath,
Expand Down Expand Up @@ -1475,7 +1497,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 @@ -1502,6 +1527,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 @@ -1567,7 +1594,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::{WitnessProgram, WitnessVersion};
use bitcoin::blockdata::constants::ChainHash;
Expand All @@ -1586,7 +1613,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 @@ -1762,7 +1789,9 @@ mod tests {
ExperimentalInvoiceRequestTlvStreamRef {
experimental_bar: None,
},
ExperimentalInvoiceTlvStreamRef {},
ExperimentalInvoiceTlvStreamRef {
experimental_baz: None,
},
),
);

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

Expand Down Expand Up @@ -2713,6 +2744,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
14 changes: 14 additions & 0 deletions lightning/src/offers/invoice_macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,5 +126,19 @@ macro_rules! invoice_accessors_common { ($self: ident, $contents: expr, $invoice
}
} }

#[cfg(test)]
macro_rules! invoice_builder_test_methods { (
$self: ident, $self_type: ty, $invoice_fields: expr, $return_type: ty, $return_value: expr
$(, $self_mut: tt)?
) => {
#[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
}
} }

pub(super) use invoice_accessors_common;
pub(super) use invoice_builder_methods_common;
#[cfg(test)]
pub(super) use invoice_builder_test_methods;
2 changes: 2 additions & 0 deletions lightning/src/offers/invoice_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1513,6 +1513,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 @@ -1605,6 +1606,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 @@ -1109,6 +1109,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 1d15a6f

Please sign in to comment.