Skip to content

Commit 3828a94

Browse files
Static invoice parsing tests
1 parent 6e275d8 commit 3828a94

File tree

1 file changed

+283
-6
lines changed

1 file changed

+283
-6
lines changed

lightning/src/offers/static_invoice.rs

Lines changed: 283 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -553,22 +553,70 @@ mod tests {
553553
use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode};
554554
use crate::ln::features::{Bolt12InvoiceFeatures, OfferFeatures};
555555
use crate::ln::inbound_payment::ExpandedKey;
556-
use crate::offers::invoice::SIGNATURE_TAG;
556+
use crate::ln::msgs::DecodeError;
557+
use crate::offers::invoice::{InvoiceTlvStreamRef, SIGNATURE_TAG};
557558
use crate::offers::merkle;
558-
use crate::offers::merkle::TaggedHash;
559-
use crate::offers::offer::{Offer, OfferBuilder, Quantity};
560-
use crate::offers::parse::Bolt12SemanticError;
559+
use crate::offers::merkle::{SignatureTlvStreamRef, TaggedHash};
560+
use crate::offers::offer::{Offer, OfferBuilder, OfferTlvStreamRef, Quantity};
561+
use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError};
561562
use crate::offers::static_invoice::{
562563
StaticInvoice, StaticInvoiceBuilder, DEFAULT_RELATIVE_EXPIRY,
563564
};
564565
use crate::offers::test_utils::*;
565566
use crate::sign::KeyMaterial;
566-
use crate::util::ser::Writeable;
567+
use crate::util::ser::{BigSize, Writeable};
567568
use bitcoin::blockdata::constants::ChainHash;
568-
use bitcoin::secp256k1::Secp256k1;
569+
use bitcoin::secp256k1::{self, Secp256k1};
569570
use bitcoin::Network;
570571
use core::time::Duration;
571572

573+
impl StaticInvoice {
574+
fn as_tlv_stream(&self) -> (OfferTlvStreamRef, InvoiceTlvStreamRef, SignatureTlvStreamRef) {
575+
(
576+
self.contents.offer.as_tlv_stream(),
577+
self.contents.as_invoice_fields_tlv_stream(),
578+
SignatureTlvStreamRef { signature: Some(&self.signature) },
579+
)
580+
}
581+
}
582+
583+
fn tlv_stream_to_bytes(
584+
tlv_stream: &(OfferTlvStreamRef, InvoiceTlvStreamRef, SignatureTlvStreamRef),
585+
) -> Vec<u8> {
586+
let mut buffer = Vec::new();
587+
tlv_stream.0.write(&mut buffer).unwrap();
588+
tlv_stream.1.write(&mut buffer).unwrap();
589+
tlv_stream.2.write(&mut buffer).unwrap();
590+
buffer
591+
}
592+
593+
fn invoice() -> StaticInvoice {
594+
let node_id = recipient_pubkey();
595+
let payment_paths = payment_paths();
596+
let now = now();
597+
let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
598+
let entropy = FixedEntropy {};
599+
let secp_ctx = Secp256k1::new();
600+
601+
let offer =
602+
OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, &entropy, &secp_ctx)
603+
.path(blinded_path())
604+
.build()
605+
.unwrap();
606+
607+
let (_offer_id, keys_opt) = offer.verify(&expanded_key, &secp_ctx).unwrap();
608+
StaticInvoiceBuilder::for_offer_using_keys(
609+
&offer,
610+
payment_paths.clone(),
611+
vec![blinded_path()],
612+
now,
613+
keys_opt.unwrap(),
614+
)
615+
.unwrap()
616+
.build_and_sign(&secp_ctx)
617+
.unwrap()
618+
}
619+
572620
fn blinded_path() -> BlindedPath {
573621
BlindedPath {
574622
introduction_node: IntroductionNode::NodeId(pubkey(40)),
@@ -851,4 +899,233 @@ mod tests {
851899
panic!("expected error")
852900
}
853901
}
902+
903+
#[test]
904+
fn parses_invoice_with_relative_expiry() {
905+
let node_id = recipient_pubkey();
906+
let payment_paths = payment_paths();
907+
let now = now();
908+
let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
909+
let entropy = FixedEntropy {};
910+
let secp_ctx = Secp256k1::new();
911+
912+
let offer =
913+
OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, &entropy, &secp_ctx)
914+
.path(blinded_path())
915+
.build()
916+
.unwrap();
917+
918+
const TEST_RELATIVE_EXPIRY: u32 = 3600;
919+
let (_offer_id, keys_opt) = offer.verify(&expanded_key, &secp_ctx).unwrap();
920+
let invoice = StaticInvoiceBuilder::for_offer_using_keys(
921+
&offer,
922+
payment_paths.clone(),
923+
vec![blinded_path()],
924+
now,
925+
keys_opt.unwrap(),
926+
)
927+
.unwrap()
928+
.relative_expiry(TEST_RELATIVE_EXPIRY)
929+
.build_and_sign(&secp_ctx)
930+
.unwrap();
931+
932+
let mut buffer = Vec::new();
933+
invoice.write(&mut buffer).unwrap();
934+
935+
match StaticInvoice::try_from(buffer) {
936+
Ok(invoice) => assert_eq!(
937+
invoice.relative_expiry(),
938+
Duration::from_secs(TEST_RELATIVE_EXPIRY as u64)
939+
),
940+
Err(e) => panic!("error parsing invoice: {:?}", e),
941+
}
942+
}
943+
944+
#[test]
945+
fn parses_invoice_with_allow_mpp() {
946+
let node_id = recipient_pubkey();
947+
let payment_paths = payment_paths();
948+
let now = now();
949+
let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
950+
let entropy = FixedEntropy {};
951+
let secp_ctx = Secp256k1::new();
952+
953+
let offer =
954+
OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, &entropy, &secp_ctx)
955+
.path(blinded_path())
956+
.build()
957+
.unwrap();
958+
959+
let (_offer_id, keys_opt) = offer.verify(&expanded_key, &secp_ctx).unwrap();
960+
let invoice = StaticInvoiceBuilder::for_offer_using_keys(
961+
&offer,
962+
payment_paths.clone(),
963+
vec![blinded_path()],
964+
now,
965+
keys_opt.unwrap(),
966+
)
967+
.unwrap()
968+
.allow_mpp()
969+
.build_and_sign(&secp_ctx)
970+
.unwrap();
971+
972+
let mut buffer = Vec::new();
973+
invoice.write(&mut buffer).unwrap();
974+
975+
match StaticInvoice::try_from(buffer) {
976+
Ok(invoice) => {
977+
let mut features = Bolt12InvoiceFeatures::empty();
978+
features.set_basic_mpp_optional();
979+
assert_eq!(invoice.invoice_features(), &features);
980+
},
981+
Err(e) => panic!("error parsing invoice: {:?}", e),
982+
}
983+
}
984+
985+
#[test]
986+
fn fails_parse_missing_invoice_fields() {
987+
// Error if `created_at` is missing.
988+
let missing_created_at_invoice = invoice();
989+
let mut tlv_stream = missing_created_at_invoice.as_tlv_stream();
990+
tlv_stream.1.created_at = None;
991+
match StaticInvoice::try_from(tlv_stream_to_bytes(&tlv_stream)) {
992+
Ok(_) => panic!("expected error"),
993+
Err(e) => {
994+
assert_eq!(
995+
e,
996+
Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingCreationTime)
997+
);
998+
},
999+
}
1000+
1001+
// Error if `node_id` is missing.
1002+
let missing_node_id_invoice = invoice();
1003+
let mut tlv_stream = missing_node_id_invoice.as_tlv_stream();
1004+
tlv_stream.1.node_id = None;
1005+
match StaticInvoice::try_from(tlv_stream_to_bytes(&tlv_stream)) {
1006+
Ok(_) => panic!("expected error"),
1007+
Err(e) => {
1008+
assert_eq!(
1009+
e,
1010+
Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingSigningPubkey)
1011+
);
1012+
},
1013+
}
1014+
1015+
// Error if message paths are missing.
1016+
let missing_message_paths_invoice = invoice();
1017+
let mut tlv_stream = missing_message_paths_invoice.as_tlv_stream();
1018+
tlv_stream.1.message_paths = None;
1019+
match StaticInvoice::try_from(tlv_stream_to_bytes(&tlv_stream)) {
1020+
Ok(_) => panic!("expected error"),
1021+
Err(e) => {
1022+
assert_eq!(
1023+
e,
1024+
Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingPaths)
1025+
);
1026+
},
1027+
}
1028+
1029+
// Error if signature is missing.
1030+
let invoice = invoice();
1031+
let mut buffer = Vec::new();
1032+
(invoice.contents.offer.as_tlv_stream(), invoice.contents.as_invoice_fields_tlv_stream())
1033+
.write(&mut buffer)
1034+
.unwrap();
1035+
match StaticInvoice::try_from(buffer) {
1036+
Ok(_) => panic!("expected error"),
1037+
Err(e) => assert_eq!(
1038+
e,
1039+
Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingSignature)
1040+
),
1041+
}
1042+
}
1043+
1044+
#[test]
1045+
fn fails_parse_invalid_signing_pubkey() {
1046+
let invoice = invoice();
1047+
let invalid_pubkey = payer_pubkey();
1048+
let mut tlv_stream = invoice.as_tlv_stream();
1049+
tlv_stream.1.node_id = Some(&invalid_pubkey);
1050+
1051+
match StaticInvoice::try_from(tlv_stream_to_bytes(&tlv_stream)) {
1052+
Ok(_) => panic!("expected error"),
1053+
Err(e) => {
1054+
assert_eq!(
1055+
e,
1056+
Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::InvalidSigningPubkey)
1057+
);
1058+
},
1059+
}
1060+
}
1061+
1062+
#[test]
1063+
fn fails_parsing_invoice_with_invalid_signature() {
1064+
let mut invoice = invoice();
1065+
let last_signature_byte = invoice.bytes.last_mut().unwrap();
1066+
*last_signature_byte = last_signature_byte.wrapping_add(1);
1067+
1068+
let mut buffer = Vec::new();
1069+
invoice.write(&mut buffer).unwrap();
1070+
1071+
match StaticInvoice::try_from(buffer) {
1072+
Ok(_) => panic!("expected error"),
1073+
Err(e) => {
1074+
assert_eq!(
1075+
e,
1076+
Bolt12ParseError::InvalidSignature(secp256k1::Error::InvalidSignature)
1077+
);
1078+
},
1079+
}
1080+
}
1081+
1082+
#[test]
1083+
fn fails_parsing_invoice_with_extra_tlv_records() {
1084+
let invoice = invoice();
1085+
let mut encoded_invoice = Vec::new();
1086+
invoice.write(&mut encoded_invoice).unwrap();
1087+
BigSize(1002).write(&mut encoded_invoice).unwrap();
1088+
BigSize(32).write(&mut encoded_invoice).unwrap();
1089+
[42u8; 32].write(&mut encoded_invoice).unwrap();
1090+
1091+
match StaticInvoice::try_from(encoded_invoice) {
1092+
Ok(_) => panic!("expected error"),
1093+
Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::InvalidValue)),
1094+
}
1095+
}
1096+
1097+
#[test]
1098+
fn fails_parsing_invoice_with_invalid_offer_fields() {
1099+
// Error if the offer is missing paths.
1100+
let missing_offer_paths_invoice = invoice();
1101+
let mut tlv_stream = missing_offer_paths_invoice.as_tlv_stream();
1102+
tlv_stream.0.paths = None;
1103+
match StaticInvoice::try_from(tlv_stream_to_bytes(&tlv_stream)) {
1104+
Ok(_) => panic!("expected error"),
1105+
Err(e) => {
1106+
assert_eq!(
1107+
e,
1108+
Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingPaths)
1109+
);
1110+
},
1111+
}
1112+
1113+
// Error if the offer has more than one chain.
1114+
let invalid_offer_chains_invoice = invoice();
1115+
let mut tlv_stream = invalid_offer_chains_invoice.as_tlv_stream();
1116+
let invalid_chains = vec![
1117+
ChainHash::using_genesis_block(Network::Bitcoin),
1118+
ChainHash::using_genesis_block(Network::Testnet),
1119+
];
1120+
tlv_stream.0.chains = Some(&invalid_chains);
1121+
match StaticInvoice::try_from(tlv_stream_to_bytes(&tlv_stream)) {
1122+
Ok(_) => panic!("expected error"),
1123+
Err(e) => {
1124+
assert_eq!(
1125+
e,
1126+
Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::UnexpectedChain)
1127+
);
1128+
},
1129+
}
1130+
}
8541131
}

0 commit comments

Comments
 (0)