InvoiceFields {
payment_paths, created_at, relative_expiry: None, payment_hash, amount_msats,
fallbacks: None, features: Bolt12InvoiceFeatures::empty(), signing_pubkey,
+ #[cfg(test)]
+ experimental_baz: None,
}
}
fallbacks: Option<Vec<FallbackAddress>>,
features: Bolt12InvoiceFeatures,
signing_pubkey: PublicKey,
+ #[cfg(test)]
+ experimental_baz: Option<u64>,
}
macro_rules! invoice_accessors { ($self: ident, $contents: expr) => {
node_id: Some(&self.signing_pubkey),
message_paths: None,
},
- ExperimentalInvoiceTlvStreamRef {},
+ ExperimentalInvoiceTlvStreamRef {
+ #[cfg(test)]
+ experimental_baz: self.experimental_baz,
+ },
)
}
}
});
/// 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,
},
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) }
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)?;
#[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;
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::*;
ExperimentalInvoiceRequestTlvStreamRef {
experimental_bar: None,
},
- ExperimentalInvoiceTlvStreamRef {},
+ ExperimentalInvoiceTlvStreamRef {
+ experimental_baz: None,
+ },
),
);
ExperimentalInvoiceRequestTlvStreamRef {
experimental_bar: None,
},
- ExperimentalInvoiceTlvStreamRef {},
+ ExperimentalInvoiceTlvStreamRef {
+ experimental_baz: None,
+ },
),
);
}
}
+ #[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 encoded_invoice = Vec::new();
+ invoice.write(&mut encoded_invoice).unwrap();
+
+ assert!(Bolt12Invoice::try_from(encoded_invoice).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();
+
+ let mut unknown_bytes = Vec::new();
+ BigSize(UNKNOWN_ODD_TYPE).write(&mut unknown_bytes).unwrap();
+ BigSize(32).write(&mut unknown_bytes).unwrap();
+ [42u8; 32].write(&mut unknown_bytes).unwrap();
+
+ unsigned_invoice.bytes.reserve_exact(
+ unsigned_invoice.bytes.capacity() - unsigned_invoice.bytes.len() + unknown_bytes.len(),
+ );
+ unsigned_invoice.experimental_bytes.extend_from_slice(&unknown_bytes);
+
+ 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.clone()) {
+ Ok(invoice) => assert_eq!(invoice.bytes, encoded_invoice),
+ Err(e) => 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();
+
+ let mut unknown_bytes = Vec::new();
+ BigSize(UNKNOWN_EVEN_TYPE).write(&mut unknown_bytes).unwrap();
+ BigSize(32).write(&mut unknown_bytes).unwrap();
+ [42u8; 32].write(&mut unknown_bytes).unwrap();
+
+ unsigned_invoice.bytes.reserve_exact(
+ unsigned_invoice.bytes.capacity() - unsigned_invoice.bytes.len() + unknown_bytes.len(),
+ );
+ unsigned_invoice.experimental_bytes.extend_from_slice(&unknown_bytes);
+
+ 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)),
+ }
+
+ 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()
+ .build().unwrap()
+ .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();
+
+ BigSize(UNKNOWN_ODD_TYPE).write(&mut encoded_invoice).unwrap();
+ BigSize(32).write(&mut encoded_invoice).unwrap();
+ [42u8; 32].write(&mut encoded_invoice).unwrap();
+
+ match Bolt12Invoice::try_from(encoded_invoice) {
+ Ok(_) => panic!("expected error"),
+ Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSignature(secp256k1::Error::IncorrectSignature)),
+ }
+ }
+
#[test]
fn fails_parsing_invoice_with_out_of_range_tlv_records() {
let invoice = OfferBuilder::new(recipient_pubkey())
ExperimentalInvoiceTlvStream, ExperimentalInvoiceTlvStreamRef, FallbackAddress,
InvoiceTlvStream, InvoiceTlvStreamRef,
};
+#[cfg(test)]
+use crate::offers::invoice_macros::invoice_builder_methods_test;
use crate::offers::invoice_macros::{invoice_accessors_common, invoice_builder_methods_common};
use crate::offers::invoice_request::InvoiceRequest;
use crate::offers::merkle::{
features: Bolt12InvoiceFeatures,
signing_pubkey: PublicKey,
message_paths: Vec<BlindedMessagePath>,
+ #[cfg(test)]
+ experimental_baz: Option<u64>,
}
/// Builds a [`StaticInvoice`] from an [`Offer`].
}
invoice_builder_methods_common!(self, Self, self.invoice, Self, self, StaticInvoice, mut);
+
+ #[cfg(test)]
+ invoice_builder_methods_test!(self, Self, self.invoice, Self, self, mut);
}
/// A semantically valid [`StaticInvoice`] that hasn't been signed.
fallbacks: None,
features: Bolt12InvoiceFeatures::empty(),
signing_pubkey,
+ #[cfg(test)]
+ experimental_baz: None,
}
}
payment_hash: None,
};
- let experimental_invoice = ExperimentalInvoiceTlvStreamRef {};
+ let experimental_invoice = ExperimentalInvoiceTlvStreamRef {
+ #[cfg(test)]
+ experimental_baz: self.experimental_baz,
+ };
let (offer, experimental_offer) = self.offer.as_tlv_stream();
amount,
},
experimental_offer_tlv_stream,
- ExperimentalInvoiceTlvStream {},
+ ExperimentalInvoiceTlvStream {
+ #[cfg(test)]
+ experimental_baz,
+ },
) = tlv_stream;
if payment_hash.is_some() {
fallbacks,
features,
signing_pubkey,
+ #[cfg(test)]
+ experimental_baz,
})
}
}
use crate::ln::inbound_payment::ExpandedKey;
use crate::ln::msgs::DecodeError;
use crate::offers::invoice::{
- ExperimentalInvoiceTlvStreamRef, InvoiceTlvStreamRef, INVOICE_TYPES,
+ ExperimentalInvoiceTlvStreamRef, InvoiceTlvStreamRef, EXPERIMENTAL_INVOICE_TYPES,
+ INVOICE_TYPES,
};
use crate::offers::merkle;
- use crate::offers::merkle::{SignatureTlvStreamRef, TaggedHash};
+ use crate::offers::merkle::{SignatureTlvStreamRef, TaggedHash, TlvStream};
use crate::offers::nonce::Nonce;
use crate::offers::offer::{
ExperimentalOfferTlvStreamRef, Offer, OfferBuilder, OfferTlvStreamRef, Quantity,
},
SignatureTlvStreamRef { signature: Some(&invoice.signature()) },
ExperimentalOfferTlvStreamRef { experimental_foo: None },
- ExperimentalInvoiceTlvStreamRef {},
+ ExperimentalInvoiceTlvStreamRef { experimental_baz: None },
)
);
}
}
+ #[test]
+ fn parses_invoice_with_experimental_tlv_records() {
+ let node_id = recipient_pubkey();
+ let payment_paths = payment_paths();
+ let now = now();
+ let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
+ let entropy = FixedEntropy {};
+ let nonce = Nonce::from_entropy_source(&entropy);
+ let secp_ctx = Secp256k1::new();
+
+ let offer = OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, nonce, &secp_ctx)
+ .path(blinded_path())
+ .build()
+ .unwrap();
+
+ let invoice = StaticInvoiceBuilder::for_offer_using_derived_keys(
+ &offer,
+ payment_paths.clone(),
+ vec![blinded_path()],
+ now,
+ &expanded_key,
+ nonce,
+ &secp_ctx,
+ )
+ .unwrap()
+ .experimental_baz(42)
+ .build_and_sign(&secp_ctx)
+ .unwrap();
+
+ let mut encoded_invoice = Vec::new();
+ invoice.write(&mut encoded_invoice).unwrap();
+
+ assert!(StaticInvoice::try_from(encoded_invoice).is_ok());
+
+ const UNKNOWN_ODD_TYPE: u64 = EXPERIMENTAL_INVOICE_TYPES.start + 1;
+ assert!(UNKNOWN_ODD_TYPE % 2 == 1);
+
+ let (mut unsigned_invoice, keys) = StaticInvoiceBuilder::for_offer_using_derived_keys(
+ &offer,
+ payment_paths.clone(),
+ vec![blinded_path()],
+ now,
+ &expanded_key,
+ nonce,
+ &secp_ctx,
+ )
+ .unwrap()
+ .build()
+ .unwrap();
+
+ let mut unknown_bytes = Vec::new();
+ BigSize(UNKNOWN_ODD_TYPE).write(&mut unknown_bytes).unwrap();
+ BigSize(32).write(&mut unknown_bytes).unwrap();
+ [42u8; 32].write(&mut unknown_bytes).unwrap();
+
+ unsigned_invoice.bytes.reserve_exact(
+ unsigned_invoice.bytes.capacity() - unsigned_invoice.bytes.len() + unknown_bytes.len(),
+ );
+ unsigned_invoice.experimental_bytes.extend_from_slice(&unknown_bytes);
+
+ 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: &UnsignedStaticInvoice| {
+ 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 StaticInvoice::try_from(encoded_invoice.clone()) {
+ Ok(invoice) => assert_eq!(invoice.bytes, encoded_invoice),
+ Err(e) => panic!("error parsing invoice: {:?}", e),
+ }
+
+ const UNKNOWN_EVEN_TYPE: u64 = EXPERIMENTAL_INVOICE_TYPES.start;
+ assert!(UNKNOWN_EVEN_TYPE % 2 == 0);
+
+ let (mut unsigned_invoice, keys) = StaticInvoiceBuilder::for_offer_using_derived_keys(
+ &offer,
+ payment_paths.clone(),
+ vec![blinded_path()],
+ now,
+ &expanded_key,
+ nonce,
+ &secp_ctx,
+ )
+ .unwrap()
+ .build()
+ .unwrap();
+
+ let mut unknown_bytes = Vec::new();
+ BigSize(UNKNOWN_EVEN_TYPE).write(&mut unknown_bytes).unwrap();
+ BigSize(32).write(&mut unknown_bytes).unwrap();
+ [42u8; 32].write(&mut unknown_bytes).unwrap();
+
+ unsigned_invoice.bytes.reserve_exact(
+ unsigned_invoice.bytes.capacity() - unsigned_invoice.bytes.len() + unknown_bytes.len(),
+ );
+ unsigned_invoice.experimental_bytes.extend_from_slice(&unknown_bytes);
+
+ 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: &UnsignedStaticInvoice| {
+ 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 StaticInvoice::try_from(encoded_invoice) {
+ Ok(_) => panic!("expected error"),
+ Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::UnknownRequiredFeature)),
+ }
+
+ let offer = OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, nonce, &secp_ctx)
+ .path(blinded_path())
+ .build()
+ .unwrap();
+
+ let invoice = StaticInvoiceBuilder::for_offer_using_derived_keys(
+ &offer,
+ payment_paths.clone(),
+ vec![blinded_path()],
+ now,
+ &expanded_key,
+ nonce,
+ &secp_ctx,
+ )
+ .unwrap()
+ .build_and_sign(&secp_ctx)
+ .unwrap();
+
+ let mut encoded_invoice = Vec::new();
+ invoice.write(&mut encoded_invoice).unwrap();
+
+ BigSize(UNKNOWN_ODD_TYPE).write(&mut encoded_invoice).unwrap();
+ BigSize(32).write(&mut encoded_invoice).unwrap();
+ [42u8; 32].write(&mut encoded_invoice).unwrap();
+
+ match StaticInvoice::try_from(encoded_invoice) {
+ Ok(_) => panic!("expected error"),
+ Err(e) => assert_eq!(
+ e,
+ Bolt12ParseError::InvalidSignature(secp256k1::Error::IncorrectSignature)
+ ),
+ }
+ }
+
#[test]
fn fails_parsing_invoice_with_out_of_range_tlv_records() {
let invoice = invoice();