Skip to content

Commit 132264a

Browse files
Static invoice building tests
1 parent c1c1502 commit 132264a

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

0 commit comments

Comments
 (0)