Skip to content

Commit 7970de4

Browse files
Static invoice building tests
1 parent 1e58066 commit 7970de4

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

0 commit comments

Comments
 (0)