From bafe4ed218dd9229bf7ad435fdc9a74e0ffb3f53 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Fri, 31 May 2024 16:17:38 -0400 Subject: [PATCH] Static invoice parsing tests --- lightning/src/offers/static_invoice.rs | 271 ++++++++++++++++++++++++- 1 file changed, 268 insertions(+), 3 deletions(-) diff --git a/lightning/src/offers/static_invoice.rs b/lightning/src/offers/static_invoice.rs index abfa2c1ea..d0846b29a 100644 --- a/lightning/src/offers/static_invoice.rs +++ b/lightning/src/offers/static_invoice.rs @@ -563,19 +563,20 @@ mod tests { use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode}; 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::merkle; use crate::offers::merkle::{SignatureTlvStreamRef, TaggedHash}; use crate::offers::offer::{Offer, OfferBuilder, OfferTlvStreamRef, Quantity}; - use crate::offers::parse::Bolt12SemanticError; + use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError}; use crate::offers::static_invoice::{ StaticInvoice, StaticInvoiceBuilder, DEFAULT_RELATIVE_EXPIRY, SIGNATURE_TAG, }; use crate::offers::test_utils::*; use crate::sign::KeyMaterial; - use crate::util::ser::{Iterable, Writeable}; + use crate::util::ser::{BigSize, Iterable, Writeable}; use bitcoin::blockdata::constants::ChainHash; - use bitcoin::secp256k1::Secp256k1; + use bitcoin::secp256k1::{self, Secp256k1}; use bitcoin::Network; use core::time::Duration; @@ -593,6 +594,43 @@ mod tests { } } + fn tlv_stream_to_bytes( + tlv_stream: &(OfferTlvStreamRef, InvoiceTlvStreamRef, SignatureTlvStreamRef), + ) -> Vec { + let mut buffer = Vec::new(); + tlv_stream.0.write(&mut buffer).unwrap(); + tlv_stream.1.write(&mut buffer).unwrap(); + tlv_stream.2.write(&mut buffer).unwrap(); + buffer + } + + fn invoice() -> StaticInvoice { + 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 secp_ctx = Secp256k1::new(); + + let offer = + OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, &entropy, &secp_ctx) + .path(blinded_path()) + .build() + .unwrap(); + + StaticInvoiceBuilder::for_offer_using_derived_keys( + &offer, + payment_paths.clone(), + vec![blinded_path()], + now, + &expanded_key, + &secp_ctx, + ) + .unwrap() + .build_and_sign(&secp_ctx) + .unwrap() + } + fn blinded_path() -> BlindedPath { BlindedPath { introduction_node: IntroductionNode::NodeId(pubkey(40)), @@ -903,4 +941,231 @@ mod tests { panic!("expected error") } } + + #[test] + fn parses_invoice_with_relative_expiry() { + 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 secp_ctx = Secp256k1::new(); + + let offer = + OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, &entropy, &secp_ctx) + .path(blinded_path()) + .build() + .unwrap(); + + const TEST_RELATIVE_EXPIRY: u32 = 3600; + let invoice = StaticInvoiceBuilder::for_offer_using_derived_keys( + &offer, + payment_paths.clone(), + vec![blinded_path()], + now, + &expanded_key, + &secp_ctx, + ) + .unwrap() + .relative_expiry(TEST_RELATIVE_EXPIRY) + .build_and_sign(&secp_ctx) + .unwrap(); + + let mut buffer = Vec::new(); + invoice.write(&mut buffer).unwrap(); + + match StaticInvoice::try_from(buffer) { + Ok(invoice) => assert_eq!( + invoice.relative_expiry(), + Duration::from_secs(TEST_RELATIVE_EXPIRY as u64) + ), + Err(e) => panic!("error parsing invoice: {:?}", e), + } + } + + #[test] + fn parses_invoice_with_allow_mpp() { + 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 secp_ctx = Secp256k1::new(); + + let offer = + OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, &entropy, &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, + &secp_ctx, + ) + .unwrap() + .allow_mpp() + .build_and_sign(&secp_ctx) + .unwrap(); + + let mut buffer = Vec::new(); + invoice.write(&mut buffer).unwrap(); + + match StaticInvoice::try_from(buffer) { + Ok(invoice) => { + let mut features = Bolt12InvoiceFeatures::empty(); + features.set_basic_mpp_optional(); + assert_eq!(invoice.invoice_features(), &features); + }, + Err(e) => panic!("error parsing invoice: {:?}", e), + } + } + + #[test] + fn fails_parsing_missing_invoice_fields() { + // Error if `created_at` is missing. + let missing_created_at_invoice = invoice(); + let mut tlv_stream = missing_created_at_invoice.as_tlv_stream(); + tlv_stream.1.created_at = None; + match StaticInvoice::try_from(tlv_stream_to_bytes(&tlv_stream)) { + Ok(_) => panic!("expected error"), + Err(e) => { + assert_eq!( + e, + Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingCreationTime) + ); + }, + } + + // Error if `node_id` is missing. + let missing_node_id_invoice = invoice(); + let mut tlv_stream = missing_node_id_invoice.as_tlv_stream(); + tlv_stream.1.node_id = None; + match StaticInvoice::try_from(tlv_stream_to_bytes(&tlv_stream)) { + Ok(_) => panic!("expected error"), + Err(e) => { + assert_eq!( + e, + Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingSigningPubkey) + ); + }, + } + + // Error if message paths are missing. + let missing_message_paths_invoice = invoice(); + let mut tlv_stream = missing_message_paths_invoice.as_tlv_stream(); + tlv_stream.1.message_paths = None; + match StaticInvoice::try_from(tlv_stream_to_bytes(&tlv_stream)) { + Ok(_) => panic!("expected error"), + Err(e) => { + assert_eq!( + e, + Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingPaths) + ); + }, + } + + // Error if signature is missing. + let invoice = invoice(); + let mut buffer = Vec::new(); + invoice.contents.as_tlv_stream().write(&mut buffer).unwrap(); + match StaticInvoice::try_from(buffer) { + Ok(_) => panic!("expected error"), + Err(e) => assert_eq!( + e, + Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingSignature) + ), + } + } + + #[test] + fn fails_parsing_invalid_signing_pubkey() { + let invoice = invoice(); + let invalid_pubkey = payer_pubkey(); + let mut tlv_stream = invoice.as_tlv_stream(); + tlv_stream.1.node_id = Some(&invalid_pubkey); + + match StaticInvoice::try_from(tlv_stream_to_bytes(&tlv_stream)) { + Ok(_) => panic!("expected error"), + Err(e) => { + assert_eq!( + e, + Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::InvalidSigningPubkey) + ); + }, + } + } + + #[test] + fn fails_parsing_invoice_with_invalid_signature() { + let mut invoice = invoice(); + let last_signature_byte = invoice.bytes.last_mut().unwrap(); + *last_signature_byte = last_signature_byte.wrapping_add(1); + + let mut buffer = Vec::new(); + invoice.write(&mut buffer).unwrap(); + + match StaticInvoice::try_from(buffer) { + Ok(_) => panic!("expected error"), + Err(e) => { + assert_eq!( + e, + Bolt12ParseError::InvalidSignature(secp256k1::Error::InvalidSignature) + ); + }, + } + } + + #[test] + fn fails_parsing_invoice_with_extra_tlv_records() { + let invoice = invoice(); + let mut encoded_invoice = Vec::new(); + invoice.write(&mut encoded_invoice).unwrap(); + BigSize(1002).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::Decode(DecodeError::InvalidValue)), + } + } + + #[test] + fn fails_parsing_invoice_with_invalid_offer_fields() { + // Error if the offer is missing paths. + let missing_offer_paths_invoice = invoice(); + let mut tlv_stream = missing_offer_paths_invoice.as_tlv_stream(); + tlv_stream.0.paths = None; + match StaticInvoice::try_from(tlv_stream_to_bytes(&tlv_stream)) { + Ok(_) => panic!("expected error"), + Err(e) => { + assert_eq!( + e, + Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingPaths) + ); + }, + } + + // Error if the offer has more than one chain. + let invalid_offer_chains_invoice = invoice(); + let mut tlv_stream = invalid_offer_chains_invoice.as_tlv_stream(); + let invalid_chains = vec![ + ChainHash::using_genesis_block(Network::Bitcoin), + ChainHash::using_genesis_block(Network::Testnet), + ]; + tlv_stream.0.chains = Some(&invalid_chains); + match StaticInvoice::try_from(tlv_stream_to_bytes(&tlv_stream)) { + Ok(_) => panic!("expected error"), + Err(e) => { + assert_eq!( + e, + Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::UnexpectedChain) + ); + }, + } + } } -- 2.39.5