Skip to content

Commit b1bc40b

Browse files
Static invoice building tests
1 parent 038a868 commit b1bc40b

File tree

1 file changed

+347
-0
lines changed

1 file changed

+347
-0
lines changed

lightning/src/offers/static_invoice.rs

Lines changed: 347 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,3 +559,350 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
559559
})
560560
}
561561
}
562+
563+
#[cfg(test)]
564+
mod tests {
565+
use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode};
566+
use crate::ln::features::{Bolt12InvoiceFeatures, OfferFeatures};
567+
use crate::ln::inbound_payment::ExpandedKey;
568+
use crate::offers::invoice::{InvoiceTlvStreamRef, SIGNATURE_TAG};
569+
use crate::offers::merkle;
570+
use crate::offers::merkle::{SignatureTlvStreamRef, TaggedHash};
571+
use crate::offers::offer::{Offer, OfferBuilder, OfferTlvStreamRef, Quantity};
572+
use crate::offers::parse::Bolt12SemanticError;
573+
use crate::offers::static_invoice::{
574+
StaticInvoice, StaticInvoiceBuilder, DEFAULT_RELATIVE_EXPIRY,
575+
};
576+
use crate::offers::test_utils::*;
577+
use crate::sign::KeyMaterial;
578+
use crate::util::ser::{Iterable, Writeable};
579+
use bitcoin::blockdata::constants::ChainHash;
580+
use bitcoin::secp256k1::Secp256k1;
581+
use bitcoin::Network;
582+
use core::time::Duration;
583+
584+
type FullInvoiceTlvStreamRef<'a> =
585+
(OfferTlvStreamRef<'a>, InvoiceTlvStreamRef<'a>, SignatureTlvStreamRef<'a>);
586+
587+
impl StaticInvoice {
588+
fn as_tlv_stream(&self) -> FullInvoiceTlvStreamRef {
589+
let (offer_tlv_stream, invoice_tlv_stream) = self.contents.as_tlv_stream();
590+
(
591+
offer_tlv_stream,
592+
invoice_tlv_stream,
593+
SignatureTlvStreamRef { signature: Some(&self.signature) },
594+
)
595+
}
596+
}
597+
598+
fn blinded_path() -> BlindedPath {
599+
BlindedPath {
600+
introduction_node: IntroductionNode::NodeId(pubkey(40)),
601+
blinding_point: pubkey(41),
602+
blinded_hops: vec![
603+
BlindedHop { blinded_node_id: pubkey(42), encrypted_payload: vec![0; 43] },
604+
BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 44] },
605+
],
606+
}
607+
}
608+
609+
#[test]
610+
fn builds_invoice_for_offer_with_defaults() {
611+
let node_id = recipient_pubkey();
612+
let payment_paths = payment_paths();
613+
let now = now();
614+
let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
615+
let entropy = FixedEntropy {};
616+
let secp_ctx = Secp256k1::new();
617+
618+
let offer =
619+
OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, &entropy, &secp_ctx)
620+
.path(blinded_path())
621+
.build()
622+
.unwrap();
623+
624+
let invoice = StaticInvoiceBuilder::for_offer_using_derived_keys(
625+
&offer,
626+
payment_paths.clone(),
627+
vec![blinded_path()],
628+
now,
629+
&expanded_key,
630+
&secp_ctx,
631+
)
632+
.unwrap()
633+
.build_and_sign(&secp_ctx)
634+
.unwrap();
635+
636+
let mut buffer = Vec::new();
637+
invoice.write(&mut buffer).unwrap();
638+
639+
assert_eq!(invoice.bytes, buffer.as_slice());
640+
assert!(invoice.metadata().is_some());
641+
assert_eq!(invoice.amount(), None);
642+
assert_eq!(invoice.description(), None);
643+
assert_eq!(invoice.offer_features(), &OfferFeatures::empty());
644+
assert_eq!(invoice.absolute_expiry(), None);
645+
assert_eq!(invoice.offer_message_paths(), &[blinded_path()]);
646+
assert_eq!(invoice.message_paths(), &[blinded_path()]);
647+
assert_eq!(invoice.issuer(), None);
648+
assert_eq!(invoice.supported_quantity(), Quantity::One);
649+
assert_ne!(invoice.signing_pubkey(), recipient_pubkey());
650+
assert_eq!(invoice.chain(), ChainHash::using_genesis_block(Network::Bitcoin));
651+
assert_eq!(invoice.payment_paths(), payment_paths.as_slice());
652+
assert_eq!(invoice.created_at(), now);
653+
assert_eq!(invoice.relative_expiry(), DEFAULT_RELATIVE_EXPIRY);
654+
#[cfg(feature = "std")]
655+
assert!(!invoice.is_expired());
656+
assert!(invoice.fallbacks().is_empty());
657+
assert_eq!(invoice.invoice_features(), &Bolt12InvoiceFeatures::empty());
658+
659+
let offer_signing_pubkey = offer.signing_pubkey().unwrap();
660+
let message = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &invoice.bytes);
661+
assert!(
662+
merkle::verify_signature(&invoice.signature, &message, offer_signing_pubkey).is_ok()
663+
);
664+
665+
let paths = vec![blinded_path()];
666+
let metadata = vec![42; 16];
667+
assert_eq!(
668+
invoice.as_tlv_stream(),
669+
(
670+
OfferTlvStreamRef {
671+
chains: None,
672+
metadata: Some(&metadata),
673+
currency: None,
674+
amount: None,
675+
description: None,
676+
features: None,
677+
absolute_expiry: None,
678+
paths: Some(&paths),
679+
issuer: None,
680+
quantity_max: None,
681+
node_id: Some(&offer_signing_pubkey),
682+
},
683+
InvoiceTlvStreamRef {
684+
paths: Some(Iterable(payment_paths.iter().map(|(_, path)| path))),
685+
blindedpay: Some(Iterable(payment_paths.iter().map(|(payinfo, _)| payinfo))),
686+
created_at: Some(now.as_secs()),
687+
relative_expiry: None,
688+
payment_hash: None,
689+
amount: None,
690+
fallbacks: None,
691+
features: None,
692+
node_id: Some(&offer_signing_pubkey),
693+
message_paths: Some(&paths),
694+
},
695+
SignatureTlvStreamRef { signature: Some(&invoice.signature()) },
696+
)
697+
);
698+
699+
if let Err(e) = StaticInvoice::try_from(buffer) {
700+
panic!("error parsing invoice: {:?}", e);
701+
}
702+
}
703+
704+
#[cfg(feature = "std")]
705+
#[test]
706+
fn builds_invoice_from_offer_with_expiration() {
707+
let node_id = recipient_pubkey();
708+
let now = now();
709+
let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
710+
let entropy = FixedEntropy {};
711+
let secp_ctx = Secp256k1::new();
712+
713+
let future_expiry = Duration::from_secs(u64::max_value());
714+
let past_expiry = Duration::from_secs(0);
715+
716+
let valid_offer =
717+
OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, &entropy, &secp_ctx)
718+
.path(blinded_path())
719+
.absolute_expiry(future_expiry)
720+
.build()
721+
.unwrap();
722+
723+
let invoice = StaticInvoiceBuilder::for_offer_using_derived_keys(
724+
&valid_offer,
725+
payment_paths(),
726+
vec![blinded_path()],
727+
now,
728+
&expanded_key,
729+
&secp_ctx,
730+
)
731+
.unwrap()
732+
.build_and_sign(&secp_ctx)
733+
.unwrap();
734+
assert!(!invoice.is_expired());
735+
assert_eq!(invoice.absolute_expiry(), Some(future_expiry));
736+
737+
let expired_offer =
738+
OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, &entropy, &secp_ctx)
739+
.path(blinded_path())
740+
.absolute_expiry(past_expiry)
741+
.build()
742+
.unwrap();
743+
if let Err(e) = StaticInvoiceBuilder::for_offer_using_derived_keys(
744+
&expired_offer,
745+
payment_paths(),
746+
vec![blinded_path()],
747+
now,
748+
&expanded_key,
749+
&secp_ctx,
750+
)
751+
.unwrap()
752+
.build_and_sign(&secp_ctx)
753+
{
754+
assert_eq!(e, Bolt12SemanticError::AlreadyExpired);
755+
} else {
756+
panic!("expected error")
757+
}
758+
}
759+
760+
#[test]
761+
fn fails_build_with_missing_paths() {
762+
let node_id = recipient_pubkey();
763+
let now = now();
764+
let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
765+
let entropy = FixedEntropy {};
766+
let secp_ctx = Secp256k1::new();
767+
768+
let valid_offer =
769+
OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, &entropy, &secp_ctx)
770+
.path(blinded_path())
771+
.build()
772+
.unwrap();
773+
774+
// Error if payment paths are missing.
775+
if let Err(e) = StaticInvoiceBuilder::for_offer_using_derived_keys(
776+
&valid_offer,
777+
Vec::new(),
778+
vec![blinded_path()],
779+
now,
780+
&expanded_key,
781+
&secp_ctx,
782+
) {
783+
assert_eq!(e, Bolt12SemanticError::MissingPaths);
784+
} else {
785+
panic!("expected error")
786+
}
787+
788+
// Error if message paths are missing.
789+
if let Err(e) = StaticInvoiceBuilder::for_offer_using_derived_keys(
790+
&valid_offer,
791+
payment_paths(),
792+
Vec::new(),
793+
now,
794+
&expanded_key,
795+
&secp_ctx,
796+
) {
797+
assert_eq!(e, Bolt12SemanticError::MissingPaths);
798+
} else {
799+
panic!("expected error")
800+
}
801+
802+
// Error if offer paths are missing.
803+
let mut offer_without_paths = valid_offer.clone();
804+
let mut offer_tlv_stream = offer_without_paths.as_tlv_stream();
805+
offer_tlv_stream.paths.take();
806+
let mut buffer = Vec::new();
807+
offer_tlv_stream.write(&mut buffer).unwrap();
808+
offer_without_paths = Offer::try_from(buffer).unwrap();
809+
if let Err(e) = StaticInvoiceBuilder::for_offer_using_derived_keys(
810+
&offer_without_paths,
811+
payment_paths(),
812+
vec![blinded_path()],
813+
now,
814+
&expanded_key,
815+
&secp_ctx,
816+
) {
817+
assert_eq!(e, Bolt12SemanticError::MissingPaths);
818+
} else {
819+
panic!("expected error")
820+
}
821+
}
822+
823+
#[test]
824+
fn fails_build_offer_signing_pubkey() {
825+
let node_id = recipient_pubkey();
826+
let now = now();
827+
let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
828+
let entropy = FixedEntropy {};
829+
let secp_ctx = Secp256k1::new();
830+
831+
let valid_offer =
832+
OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, &entropy, &secp_ctx)
833+
.path(blinded_path())
834+
.build()
835+
.unwrap();
836+
837+
// Error if offer signing pubkey is missing.
838+
let mut offer_missing_signing_pubkey = valid_offer.clone();
839+
let mut offer_tlv_stream = offer_missing_signing_pubkey.as_tlv_stream();
840+
offer_tlv_stream.node_id.take();
841+
let mut buffer = Vec::new();
842+
offer_tlv_stream.write(&mut buffer).unwrap();
843+
offer_missing_signing_pubkey = Offer::try_from(buffer).unwrap();
844+
845+
if let Err(e) = StaticInvoiceBuilder::for_offer_using_derived_keys(
846+
&offer_missing_signing_pubkey,
847+
payment_paths(),
848+
vec![blinded_path()],
849+
now,
850+
&expanded_key,
851+
&secp_ctx,
852+
) {
853+
assert_eq!(e, Bolt12SemanticError::MissingSigningPubkey);
854+
} else {
855+
panic!("expected error")
856+
}
857+
858+
// Error if the offer's metadata cannot be verified.
859+
let offer = OfferBuilder::new(recipient_pubkey())
860+
.path(blinded_path())
861+
.metadata(vec![42; 32])
862+
.unwrap()
863+
.build()
864+
.unwrap();
865+
if let Err(e) = StaticInvoiceBuilder::for_offer_using_derived_keys(
866+
&offer,
867+
payment_paths(),
868+
vec![blinded_path()],
869+
now,
870+
&expanded_key,
871+
&secp_ctx,
872+
) {
873+
assert_eq!(e, Bolt12SemanticError::InvalidMetadata);
874+
} else {
875+
panic!("expected error")
876+
}
877+
}
878+
879+
#[test]
880+
fn fails_building_with_extra_offer_chains() {
881+
let node_id = recipient_pubkey();
882+
let now = now();
883+
let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
884+
let entropy = FixedEntropy {};
885+
let secp_ctx = Secp256k1::new();
886+
887+
let offer_with_extra_chain =
888+
OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, &entropy, &secp_ctx)
889+
.path(blinded_path())
890+
.chain(Network::Bitcoin)
891+
.chain(Network::Testnet)
892+
.build()
893+
.unwrap();
894+
895+
if let Err(e) = StaticInvoiceBuilder::for_offer_using_derived_keys(
896+
&offer_with_extra_chain,
897+
payment_paths(),
898+
vec![blinded_path()],
899+
now,
900+
&expanded_key,
901+
&secp_ctx,
902+
) {
903+
assert_eq!(e, Bolt12SemanticError::UnexpectedChain);
904+
} else {
905+
panic!("expected error")
906+
}
907+
}
908+
}

0 commit comments

Comments
 (0)