}
}
-tlv_stream!(InvoiceTlvStream, InvoiceTlvStreamRef, 160..240, {
+/// Valid type range for invoice TLV records.
+pub(super) const INVOICE_TYPES: core::ops::Range<u64> = 160..240;
+
+tlv_stream!(InvoiceTlvStream, InvoiceTlvStreamRef, INVOICE_TYPES, {
(160, paths: (Vec<BlindedPath>, WithoutLength, Iterable<'a, BlindedPathIter<'a>, BlindedPath>)),
(162, blindedpay: (Vec<BlindedPayInfo>, WithoutLength, Iterable<'a, BlindedPayInfoIter<'a>, BlindedPayInfo>)),
(164, created_at: (u64, HighZeroBytesDroppedBigSize)),
(174, features: (Bolt12InvoiceFeatures, WithoutLength)),
(176, node_id: PublicKey),
// Only present in `StaticInvoice`s.
- (238, message_paths: (Vec<BlindedMessagePath>, WithoutLength)),
+ (236, message_paths: (Vec<BlindedMessagePath>, WithoutLength)),
});
pub(super) type BlindedPathIter<'a> = core::iter::Map<
#[cfg(test)]
mod tests {
- use super::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, FallbackAddress, FullInvoiceTlvStreamRef, InvoiceTlvStreamRef, SIGNATURE_TAG, UnsignedBolt12Invoice};
+ use super::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, FallbackAddress, FullInvoiceTlvStreamRef, INVOICE_TYPES, InvoiceTlvStreamRef, SIGNATURE_TAG, UnsignedBolt12Invoice};
use bitcoin::{CompressedPublicKey, WitnessProgram, WitnessVersion};
use bitcoin::constants::ChainHash;
}
#[test]
- fn fails_parsing_invoice_with_extra_tlv_records() {
+ fn parses_invoice_with_unknown_tlv_records() {
+ const UNKNOWN_ODD_TYPE: u64 = INVOICE_TYPES.end - 1;
+ assert!(UNKNOWN_ODD_TYPE % 2 == 1);
+
+ let secp_ctx = Secp256k1::new();
+ let keys = Keypair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
+ 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.bytes).unwrap();
+ BigSize(32).write(&mut unsigned_invoice.bytes).unwrap();
+ [42u8; 32].write(&mut unsigned_invoice.bytes).unwrap();
+
+ unsigned_invoice.tagged_hash =
+ TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &unsigned_invoice.bytes);
+
+ 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 = INVOICE_TYPES.end - 2;
+ 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.bytes).unwrap();
+ BigSize(32).write(&mut unsigned_invoice.bytes).unwrap();
+ [42u8; 32].write(&mut unsigned_invoice.bytes).unwrap();
+
+ unsigned_invoice.tagged_hash =
+ TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &unsigned_invoice.bytes);
+
+ 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())
.amount_msats(1000)
.build().unwrap()
#[cfg(test)]
mod tests {
- use super::{InvoiceRequest, InvoiceRequestFields, InvoiceRequestTlvStreamRef, PAYER_NOTE_LIMIT, SIGNATURE_TAG, UnsignedInvoiceRequest};
+ use super::{INVOICE_REQUEST_TYPES, InvoiceRequest, InvoiceRequestFields, InvoiceRequestTlvStreamRef, PAYER_NOTE_LIMIT, SIGNATURE_TAG, UnsignedInvoiceRequest};
use bitcoin::constants::ChainHash;
use bitcoin::network::Network;
}
#[test]
- fn fails_parsing_invoice_request_with_extra_tlv_records() {
+ fn parses_invoice_request_with_unknown_tlv_records() {
+ const UNKNOWN_ODD_TYPE: u64 = INVOICE_REQUEST_TYPES.end - 1;
+ assert!(UNKNOWN_ODD_TYPE % 2 == 1);
+
+ let secp_ctx = Secp256k1::new();
+ let keys = Keypair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
+ let mut unsigned_invoice_request = OfferBuilder::new(keys.public_key())
+ .amount_msats(1000)
+ .build().unwrap()
+ .request_invoice(vec![1; 32], keys.public_key()).unwrap()
+ .build().unwrap();
+
+ BigSize(UNKNOWN_ODD_TYPE).write(&mut unsigned_invoice_request.bytes).unwrap();
+ BigSize(32).write(&mut unsigned_invoice_request.bytes).unwrap();
+ [42u8; 32].write(&mut unsigned_invoice_request.bytes).unwrap();
+
+ unsigned_invoice_request.tagged_hash =
+ TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &unsigned_invoice_request.bytes);
+
+ let invoice_request = unsigned_invoice_request
+ .sign(|message: &UnsignedInvoiceRequest|
+ Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
+ )
+ .unwrap();
+
+ let mut encoded_invoice_request = Vec::new();
+ invoice_request.write(&mut encoded_invoice_request).unwrap();
+
+ match InvoiceRequest::try_from(encoded_invoice_request.clone()) {
+ Ok(invoice_request) => assert_eq!(invoice_request.bytes, encoded_invoice_request),
+ Err(e) => panic!("error parsing invoice_request: {:?}", e),
+ }
+
+ const UNKNOWN_EVEN_TYPE: u64 = INVOICE_REQUEST_TYPES.end - 2;
+ assert!(UNKNOWN_EVEN_TYPE % 2 == 0);
+
+ let mut unsigned_invoice_request = OfferBuilder::new(keys.public_key())
+ .amount_msats(1000)
+ .build().unwrap()
+ .request_invoice(vec![1; 32], keys.public_key()).unwrap()
+ .build().unwrap();
+
+ BigSize(UNKNOWN_EVEN_TYPE).write(&mut unsigned_invoice_request.bytes).unwrap();
+ BigSize(32).write(&mut unsigned_invoice_request.bytes).unwrap();
+ [42u8; 32].write(&mut unsigned_invoice_request.bytes).unwrap();
+
+ unsigned_invoice_request.tagged_hash =
+ TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &unsigned_invoice_request.bytes);
+
+ let invoice_request = unsigned_invoice_request
+ .sign(|message: &UnsignedInvoiceRequest|
+ Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
+ )
+ .unwrap();
+
+ let mut encoded_invoice_request = Vec::new();
+ invoice_request.write(&mut encoded_invoice_request).unwrap();
+
+ match InvoiceRequest::try_from(encoded_invoice_request) {
+ Ok(_) => panic!("expected error"),
+ Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::UnknownRequiredFeature)),
+ }
+ }
+
+ #[test]
+ fn fails_parsing_invoice_request_with_out_of_range_tlv_records() {
let secp_ctx = Secp256k1::new();
let keys = Keypair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
let invoice_request = OfferBuilder::new(keys.public_key())
#[cfg(test)]
mod tests {
- use super::{Amount, Offer, OfferTlvStreamRef, Quantity};
+ use super::{Amount, OFFER_TYPES, Offer, OfferTlvStreamRef, Quantity};
#[cfg(not(c_bindings))]
use {
super::OfferBuilder,
}
#[test]
- fn fails_parsing_offer_with_extra_tlv_records() {
+ fn parses_offer_with_unknown_tlv_records() {
+ const UNKNOWN_ODD_TYPE: u64 = OFFER_TYPES.end - 1;
+ assert!(UNKNOWN_ODD_TYPE % 2 == 1);
+
+ let offer = OfferBuilder::new(pubkey(42)).build().unwrap();
+
+ let mut encoded_offer = Vec::new();
+ offer.write(&mut encoded_offer).unwrap();
+ BigSize(UNKNOWN_ODD_TYPE).write(&mut encoded_offer).unwrap();
+ BigSize(32).write(&mut encoded_offer).unwrap();
+ [42u8; 32].write(&mut encoded_offer).unwrap();
+
+ match Offer::try_from(encoded_offer.clone()) {
+ Ok(offer) => assert_eq!(offer.bytes, encoded_offer),
+ Err(e) => panic!("error parsing offer: {:?}", e),
+ }
+
+ const UNKNOWN_EVEN_TYPE: u64 = OFFER_TYPES.end - 2;
+ assert!(UNKNOWN_EVEN_TYPE % 2 == 0);
+
+ let offer = OfferBuilder::new(pubkey(42)).build().unwrap();
+
+ let mut encoded_offer = Vec::new();
+ offer.write(&mut encoded_offer).unwrap();
+ BigSize(UNKNOWN_EVEN_TYPE).write(&mut encoded_offer).unwrap();
+ BigSize(32).write(&mut encoded_offer).unwrap();
+ [42u8; 32].write(&mut encoded_offer).unwrap();
+
+ match Offer::try_from(encoded_offer) {
+ Ok(_) => panic!("expected error"),
+ Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::UnknownRequiredFeature)),
+ }
+ }
+
+ #[test]
+ fn fails_parsing_offer_with_out_of_range_tlv_records() {
let offer = OfferBuilder::new(pubkey(42)).build().unwrap();
let mut encoded_offer = Vec::new();
offer.write(&mut encoded_offer).unwrap();
- BigSize(80).write(&mut encoded_offer).unwrap();
+ BigSize(OFFER_TYPES.end).write(&mut encoded_offer).unwrap();
BigSize(32).write(&mut encoded_offer).unwrap();
[42u8; 32].write(&mut encoded_offer).unwrap();
use crate::ln::features::{InvoiceRequestFeatures, OfferFeatures};
use crate::ln::inbound_payment::ExpandedKey;
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
- use crate::offers::invoice_request::InvoiceRequestTlvStreamRef;
+ use crate::offers::invoice_request::{INVOICE_REQUEST_TYPES, InvoiceRequestTlvStreamRef};
use crate::offers::nonce::Nonce;
use crate::offers::offer::OfferTlvStreamRef;
use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError};
}
#[test]
- fn fails_parsing_refund_with_extra_tlv_records() {
+ fn parses_refund_with_unknown_tlv_records() {
+ const UNKNOWN_ODD_TYPE: u64 = INVOICE_REQUEST_TYPES.end - 1;
+ assert!(UNKNOWN_ODD_TYPE % 2 == 1);
+
+ let refund = RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap()
+ .build().unwrap();
+
+ let mut encoded_refund = Vec::new();
+ refund.write(&mut encoded_refund).unwrap();
+ BigSize(UNKNOWN_ODD_TYPE).write(&mut encoded_refund).unwrap();
+ BigSize(32).write(&mut encoded_refund).unwrap();
+ [42u8; 32].write(&mut encoded_refund).unwrap();
+
+ match Refund::try_from(encoded_refund.clone()) {
+ Ok(refund) => assert_eq!(refund.bytes, encoded_refund),
+ Err(e) => panic!("error parsing refund: {:?}", e),
+ }
+
+ const UNKNOWN_EVEN_TYPE: u64 = INVOICE_REQUEST_TYPES.end - 2;
+ assert!(UNKNOWN_EVEN_TYPE % 2 == 0);
+
+ let refund = RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap()
+ .build().unwrap();
+
+ let mut encoded_refund = Vec::new();
+ refund.write(&mut encoded_refund).unwrap();
+ BigSize(UNKNOWN_EVEN_TYPE).write(&mut encoded_refund).unwrap();
+ BigSize(32).write(&mut encoded_refund).unwrap();
+ [42u8; 32].write(&mut encoded_refund).unwrap();
+
+ match Refund::try_from(encoded_refund) {
+ Ok(_) => panic!("expected error"),
+ Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::UnknownRequiredFeature)),
+ }
+ }
+
+ #[test]
+ fn fails_parsing_refund_with_out_of_range_tlv_records() {
let secp_ctx = Secp256k1::new();
let keys = Keypair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
let refund = RefundBuilder::new(vec![1; 32], keys.public_key(), 1000).unwrap()
Ok(Self { offer_bytes: &offer.bytes, invoice, keys })
}
- /// Builds a signed [`StaticInvoice`] after checking for valid semantics.
- pub fn build_and_sign<T: secp256k1::Signing>(
- self, secp_ctx: &Secp256k1<T>,
- ) -> Result<StaticInvoice, Bolt12SemanticError> {
+ /// Builds an [`UnsignedStaticInvoice`] after checking for valid semantics, returning it along with
+ /// the [`Keypair`] needed to sign it.
+ pub fn build(self) -> Result<(UnsignedStaticInvoice, Keypair), Bolt12SemanticError> {
#[cfg(feature = "std")]
{
if self.invoice.is_offer_expired() {
}
let Self { offer_bytes, invoice, keys } = self;
- let unsigned_invoice = UnsignedStaticInvoice::new(&offer_bytes, invoice);
+ Ok((UnsignedStaticInvoice::new(&offer_bytes, invoice), keys))
+ }
+
+ /// Builds a signed [`StaticInvoice`] after checking for valid semantics.
+ pub fn build_and_sign<T: secp256k1::Signing>(
+ self, secp_ctx: &Secp256k1<T>,
+ ) -> Result<StaticInvoice, Bolt12SemanticError> {
+ let (unsigned_invoice, keys) = self.build()?;
let invoice = unsigned_invoice
.sign(|message: &UnsignedStaticInvoice| {
Ok(secp_ctx.sign_schnorr_no_aux_rand(message.tagged_hash.as_digest(), &keys))
use crate::ln::features::{Bolt12InvoiceFeatures, OfferFeatures};
use crate::ln::inbound_payment::ExpandedKey;
use crate::ln::msgs::DecodeError;
- use crate::offers::invoice::InvoiceTlvStreamRef;
+ use crate::offers::invoice::{InvoiceTlvStreamRef, INVOICE_TYPES};
use crate::offers::merkle;
use crate::offers::merkle::{SignatureTlvStreamRef, TaggedHash};
use crate::offers::nonce::Nonce;
use crate::offers::offer::{Offer, OfferBuilder, OfferTlvStreamRef, Quantity};
use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError};
use crate::offers::static_invoice::{
- StaticInvoice, StaticInvoiceBuilder, DEFAULT_RELATIVE_EXPIRY, SIGNATURE_TAG,
+ StaticInvoice, StaticInvoiceBuilder, UnsignedStaticInvoice, DEFAULT_RELATIVE_EXPIRY,
+ SIGNATURE_TAG,
};
use crate::offers::test_utils::*;
use crate::sign::KeyMaterial;
}
#[test]
- fn fails_parsing_invoice_with_extra_tlv_records() {
+ fn parses_invoice_with_unknown_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();
+
+ const UNKNOWN_ODD_TYPE: u64 = INVOICE_TYPES.end - 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();
+
+ BigSize(UNKNOWN_ODD_TYPE).write(&mut unsigned_invoice.bytes).unwrap();
+ BigSize(32).write(&mut unsigned_invoice.bytes).unwrap();
+ [42u8; 32].write(&mut unsigned_invoice.bytes).unwrap();
+
+ unsigned_invoice.tagged_hash =
+ TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &unsigned_invoice.bytes);
+
+ 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 = INVOICE_TYPES.end - 2;
+ 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();
+
+ BigSize(UNKNOWN_EVEN_TYPE).write(&mut unsigned_invoice.bytes).unwrap();
+ BigSize(32).write(&mut unsigned_invoice.bytes).unwrap();
+ [42u8; 32].write(&mut unsigned_invoice.bytes).unwrap();
+
+ unsigned_invoice.tagged_hash =
+ TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &unsigned_invoice.bytes);
+
+ 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)),
+ }
+ }
+
+ #[test]
+ fn fails_parsing_invoice_with_out_of_range_tlv_records() {
let invoice = invoice();
let mut encoded_invoice = Vec::new();
invoice.write(&mut encoded_invoice).unwrap();