Skip to content

Commit 555b9c0

Browse files
Static invoice building tests
1 parent ac5a385 commit 555b9c0

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

0 commit comments

Comments
 (0)