Skip to content

Commit e8c1a0d

Browse files
Static invoice building tests
1 parent 7fe69fd commit e8c1a0d

File tree

2 files changed

+308
-0
lines changed

2 files changed

+308
-0
lines changed

lightning/src/offers/offer.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -664,6 +664,13 @@ impl Offer {
664664
pub fn expects_quantity(&self) -> bool {
665665
self.contents.expects_quantity()
666666
}
667+
668+
#[cfg(test)]
669+
pub(crate) fn verify<T: secp256k1::Signing>(
670+
&self, key: &ExpandedKey, secp_ctx: &Secp256k1<T>
671+
) -> Result<(OfferId, Option<KeyPair>), ()> {
672+
self.contents.verify(&self.bytes, key, secp_ctx)
673+
}
667674
}
668675

669676
macro_rules! request_invoice_derived_payer_id { ($self: ident, $builder: ty) => {

lightning/src/offers/static_invoice.rs

Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,3 +546,304 @@ impl TryFrom<(OfferTlvStream, InvoiceTlvStream)> for InvoiceContents {
546546
})
547547
}
548548
}
549+
550+
#[cfg(test)]
551+
mod tests {
552+
use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode};
553+
use crate::ln::features::{Bolt12InvoiceFeatures, OfferFeatures};
554+
use crate::ln::inbound_payment::ExpandedKey;
555+
use crate::offers::invoice::SIGNATURE_TAG;
556+
use crate::offers::merkle;
557+
use crate::offers::merkle::TaggedHash;
558+
use crate::offers::offer::{Offer, OfferBuilder, Quantity};
559+
use crate::offers::parse::Bolt12SemanticError;
560+
use crate::offers::static_invoice::{
561+
StaticInvoice, StaticInvoiceBuilder, DEFAULT_RELATIVE_EXPIRY,
562+
};
563+
use crate::offers::test_utils::*;
564+
use crate::sign::KeyMaterial;
565+
use crate::util::ser::Writeable;
566+
use bitcoin::blockdata::constants::ChainHash;
567+
use bitcoin::network::constants::Network;
568+
use bitcoin::secp256k1::Secp256k1;
569+
use core::time::Duration;
570+
571+
fn blinded_path() -> BlindedPath {
572+
BlindedPath {
573+
introduction_node: IntroductionNode::NodeId(pubkey(40)),
574+
blinding_point: pubkey(41),
575+
blinded_hops: vec![
576+
BlindedHop { blinded_node_id: pubkey(42), encrypted_payload: vec![0; 43] },
577+
BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 44] },
578+
],
579+
}
580+
}
581+
582+
#[test]
583+
fn builds_invoice_for_offer_with_defaults() {
584+
let node_id = recipient_pubkey();
585+
let payment_paths = payment_paths();
586+
let now = now();
587+
let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
588+
let entropy = FixedEntropy {};
589+
let secp_ctx = Secp256k1::new();
590+
591+
let offer =
592+
OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, &entropy, &secp_ctx)
593+
.path(blinded_path())
594+
.build()
595+
.unwrap();
596+
597+
let (_offer_id, keys_opt) = offer.verify(&expanded_key, &secp_ctx).unwrap();
598+
let invoice = StaticInvoiceBuilder::for_offer_using_keys(
599+
&offer,
600+
payment_paths.clone(),
601+
vec![blinded_path()],
602+
now,
603+
keys_opt.unwrap(),
604+
)
605+
.unwrap()
606+
.build_and_sign(&secp_ctx)
607+
.unwrap();
608+
609+
let mut buffer = Vec::new();
610+
invoice.write(&mut buffer).unwrap();
611+
612+
assert_eq!(invoice.bytes, buffer.as_slice());
613+
assert!(invoice.metadata().is_some());
614+
assert_eq!(invoice.amount(), None);
615+
assert_eq!(invoice.description(), None);
616+
assert_eq!(invoice.offer_features(), &OfferFeatures::empty());
617+
assert_eq!(invoice.absolute_expiry(), None);
618+
assert_eq!(invoice.offer_message_paths(), &[blinded_path()]);
619+
assert_eq!(invoice.message_paths(), &[blinded_path()]);
620+
assert_eq!(invoice.issuer(), None);
621+
assert_eq!(invoice.supported_quantity(), Quantity::One);
622+
assert_ne!(invoice.signing_pubkey(), recipient_pubkey());
623+
assert_eq!(invoice.chain(), ChainHash::using_genesis_block(Network::Bitcoin));
624+
assert_eq!(invoice.payment_paths(), payment_paths.as_slice());
625+
assert_eq!(invoice.created_at(), now);
626+
assert_eq!(invoice.relative_expiry(), DEFAULT_RELATIVE_EXPIRY);
627+
#[cfg(feature = "std")]
628+
assert!(!invoice.is_expired());
629+
assert_eq!(invoice.fallbacks(), vec![]);
630+
assert_eq!(invoice.invoice_features(), &Bolt12InvoiceFeatures::empty());
631+
632+
let message = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &invoice.bytes);
633+
assert!(merkle::verify_signature(
634+
&invoice.signature,
635+
&message,
636+
keys_opt.unwrap().public_key()
637+
)
638+
.is_ok());
639+
640+
if let Err(e) = StaticInvoice::try_from(buffer) {
641+
panic!("error parsing invoice: {:?}", e);
642+
}
643+
}
644+
645+
#[cfg(feature = "std")]
646+
#[test]
647+
fn builds_invoice_from_offer_with_expiration() {
648+
let node_id = recipient_pubkey();
649+
let now = now();
650+
let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
651+
let entropy = FixedEntropy {};
652+
let secp_ctx = Secp256k1::new();
653+
654+
let future_expiry = Duration::from_secs(u64::max_value());
655+
let past_expiry = Duration::from_secs(0);
656+
657+
let valid_offer =
658+
OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, &entropy, &secp_ctx)
659+
.path(blinded_path())
660+
.absolute_expiry(future_expiry)
661+
.build()
662+
.unwrap();
663+
664+
let (_offer_id, keys_opt) = valid_offer.verify(&expanded_key, &secp_ctx).unwrap();
665+
let invoice = StaticInvoiceBuilder::for_offer_using_keys(
666+
&valid_offer,
667+
payment_paths(),
668+
vec![blinded_path()],
669+
now,
670+
keys_opt.unwrap(),
671+
)
672+
.unwrap()
673+
.build_and_sign(&secp_ctx)
674+
.unwrap();
675+
assert!(!invoice.is_expired());
676+
assert_eq!(invoice.absolute_expiry(), Some(future_expiry));
677+
678+
let expired_offer =
679+
OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, &entropy, &secp_ctx)
680+
.path(blinded_path())
681+
.absolute_expiry(past_expiry)
682+
.build()
683+
.unwrap();
684+
let (_offer_id, keys_opt) = expired_offer.verify(&expanded_key, &secp_ctx).unwrap();
685+
if let Err(e) = StaticInvoiceBuilder::for_offer_using_keys(
686+
&expired_offer,
687+
payment_paths(),
688+
vec![blinded_path()],
689+
now,
690+
keys_opt.unwrap(),
691+
)
692+
.unwrap()
693+
.build_and_sign(&secp_ctx)
694+
{
695+
assert_eq!(e, Bolt12SemanticError::AlreadyExpired);
696+
} else {
697+
panic!("expected error")
698+
}
699+
}
700+
701+
#[test]
702+
fn fails_build_with_missing_paths() {
703+
let node_id = recipient_pubkey();
704+
let now = now();
705+
let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
706+
let entropy = FixedEntropy {};
707+
let secp_ctx = Secp256k1::new();
708+
709+
let valid_offer =
710+
OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, &entropy, &secp_ctx)
711+
.path(blinded_path())
712+
.build()
713+
.unwrap();
714+
let (_offer_id, keys_opt) = valid_offer.verify(&expanded_key, &secp_ctx).unwrap();
715+
716+
// Error if payment paths are missing.
717+
if let Err(e) = StaticInvoiceBuilder::for_offer_using_keys(
718+
&valid_offer,
719+
Vec::new(),
720+
vec![blinded_path()],
721+
now,
722+
keys_opt.unwrap(),
723+
) {
724+
assert_eq!(e, Bolt12SemanticError::MissingPaths);
725+
} else {
726+
panic!("expected error")
727+
}
728+
729+
// Error if message paths are missing.
730+
if let Err(e) = StaticInvoiceBuilder::for_offer_using_keys(
731+
&valid_offer,
732+
payment_paths(),
733+
Vec::new(),
734+
now,
735+
keys_opt.unwrap(),
736+
) {
737+
assert_eq!(e, Bolt12SemanticError::MissingPaths);
738+
} else {
739+
panic!("expected error")
740+
}
741+
742+
// Error if offer paths are missing.
743+
let mut offer_without_paths = valid_offer.clone();
744+
let mut offer_tlv_stream = offer_without_paths.as_tlv_stream();
745+
offer_tlv_stream.paths.take();
746+
let mut buffer = Vec::new();
747+
offer_tlv_stream.write(&mut buffer).unwrap();
748+
offer_without_paths = Offer::try_from(buffer).unwrap();
749+
if let Err(e) = StaticInvoiceBuilder::for_offer_using_keys(
750+
&offer_without_paths,
751+
payment_paths(),
752+
vec![blinded_path()],
753+
now,
754+
keys_opt.unwrap(),
755+
) {
756+
assert_eq!(e, Bolt12SemanticError::MissingPaths);
757+
} else {
758+
panic!("expected error")
759+
}
760+
}
761+
762+
#[test]
763+
fn fails_build_offer_signing_pubkey() {
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+
let (_offer_id, keys_opt) = valid_offer.verify(&expanded_key, &secp_ctx).unwrap();
776+
777+
// Error if offer signing pubkey is missing.
778+
let mut offer_missing_signing_pubkey = valid_offer.clone();
779+
let mut offer_tlv_stream = offer_missing_signing_pubkey.as_tlv_stream();
780+
offer_tlv_stream.node_id.take();
781+
let mut buffer = Vec::new();
782+
offer_tlv_stream.write(&mut buffer).unwrap();
783+
offer_missing_signing_pubkey = Offer::try_from(buffer).unwrap();
784+
785+
if let Err(e) = StaticInvoiceBuilder::for_offer_using_keys(
786+
&offer_missing_signing_pubkey,
787+
payment_paths(),
788+
vec![blinded_path()],
789+
now,
790+
keys_opt.unwrap(),
791+
) {
792+
assert_eq!(e, Bolt12SemanticError::MissingSigningPubkey);
793+
} else {
794+
panic!("expected error")
795+
}
796+
797+
// Error if the offer's signing pubkey doesn't match the invoice's.
798+
let mut offer_invalid_signing_pubkey = valid_offer.clone();
799+
let mut offer_tlv_stream = offer_invalid_signing_pubkey.as_tlv_stream();
800+
let invalid_node_id = payer_pubkey();
801+
offer_tlv_stream.node_id = Some(&invalid_node_id);
802+
let mut buffer = Vec::new();
803+
offer_tlv_stream.write(&mut buffer).unwrap();
804+
offer_invalid_signing_pubkey = Offer::try_from(buffer).unwrap();
805+
806+
if let Err(e) = StaticInvoiceBuilder::for_offer_using_keys(
807+
&offer_invalid_signing_pubkey,
808+
payment_paths(),
809+
vec![blinded_path()],
810+
now,
811+
keys_opt.unwrap(),
812+
) {
813+
assert_eq!(e, Bolt12SemanticError::InvalidSigningPubkey);
814+
} else {
815+
panic!("expected error")
816+
}
817+
}
818+
819+
#[test]
820+
fn fails_build_with_extra_offer_chains() {
821+
let node_id = recipient_pubkey();
822+
let now = now();
823+
let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
824+
let entropy = FixedEntropy {};
825+
let secp_ctx = Secp256k1::new();
826+
827+
let offer_with_extra_chain =
828+
OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, &entropy, &secp_ctx)
829+
.path(blinded_path())
830+
.chain(Network::Bitcoin)
831+
.chain(Network::Testnet)
832+
.build()
833+
.unwrap();
834+
let (_offer_id, keys_opt) =
835+
offer_with_extra_chain.verify(&expanded_key, &secp_ctx).unwrap();
836+
837+
if let Err(e) = StaticInvoiceBuilder::for_offer_using_keys(
838+
&offer_with_extra_chain,
839+
payment_paths(),
840+
vec![blinded_path()],
841+
now,
842+
keys_opt.unwrap(),
843+
) {
844+
assert_eq!(e, Bolt12SemanticError::UnexpectedChain);
845+
} else {
846+
panic!("expected error")
847+
}
848+
}
849+
}

0 commit comments

Comments
 (0)