Skip to content

Commit dc2f56f

Browse files
Static invoice building tests
1 parent bf9d599 commit dc2f56f

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

0 commit comments

Comments
 (0)