Skip to content

Commit fd8b053

Browse files
Static invoice parsing tests
1 parent b1bc40b commit fd8b053

File tree

1 file changed

+268
-3
lines changed

1 file changed

+268
-3
lines changed

lightning/src/offers/static_invoice.rs

+268-3
Original file line numberDiff line numberDiff line change
@@ -565,19 +565,20 @@ mod tests {
565565
use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode};
566566
use crate::ln::features::{Bolt12InvoiceFeatures, OfferFeatures};
567567
use crate::ln::inbound_payment::ExpandedKey;
568+
use crate::ln::msgs::DecodeError;
568569
use crate::offers::invoice::{InvoiceTlvStreamRef, SIGNATURE_TAG};
569570
use crate::offers::merkle;
570571
use crate::offers::merkle::{SignatureTlvStreamRef, TaggedHash};
571572
use crate::offers::offer::{Offer, OfferBuilder, OfferTlvStreamRef, Quantity};
572-
use crate::offers::parse::Bolt12SemanticError;
573+
use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError};
573574
use crate::offers::static_invoice::{
574575
StaticInvoice, StaticInvoiceBuilder, DEFAULT_RELATIVE_EXPIRY,
575576
};
576577
use crate::offers::test_utils::*;
577578
use crate::sign::KeyMaterial;
578-
use crate::util::ser::{Iterable, Writeable};
579+
use crate::util::ser::{BigSize, Iterable, Writeable};
579580
use bitcoin::blockdata::constants::ChainHash;
580-
use bitcoin::secp256k1::Secp256k1;
581+
use bitcoin::secp256k1::{self, Secp256k1};
581582
use bitcoin::Network;
582583
use core::time::Duration;
583584

@@ -595,6 +596,43 @@ mod tests {
595596
}
596597
}
597598

599+
fn tlv_stream_to_bytes(
600+
tlv_stream: &(OfferTlvStreamRef, InvoiceTlvStreamRef, SignatureTlvStreamRef),
601+
) -> Vec<u8> {
602+
let mut buffer = Vec::new();
603+
tlv_stream.0.write(&mut buffer).unwrap();
604+
tlv_stream.1.write(&mut buffer).unwrap();
605+
tlv_stream.2.write(&mut buffer).unwrap();
606+
buffer
607+
}
608+
609+
fn invoice() -> StaticInvoice {
610+
let node_id = recipient_pubkey();
611+
let payment_paths = payment_paths();
612+
let now = now();
613+
let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
614+
let entropy = FixedEntropy {};
615+
let secp_ctx = Secp256k1::new();
616+
617+
let offer =
618+
OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, &entropy, &secp_ctx)
619+
.path(blinded_path())
620+
.build()
621+
.unwrap();
622+
623+
StaticInvoiceBuilder::for_offer_using_derived_keys(
624+
&offer,
625+
payment_paths.clone(),
626+
vec![blinded_path()],
627+
now,
628+
&expanded_key,
629+
&secp_ctx,
630+
)
631+
.unwrap()
632+
.build_and_sign(&secp_ctx)
633+
.unwrap()
634+
}
635+
598636
fn blinded_path() -> BlindedPath {
599637
BlindedPath {
600638
introduction_node: IntroductionNode::NodeId(pubkey(40)),
@@ -905,4 +943,231 @@ mod tests {
905943
panic!("expected error")
906944
}
907945
}
946+
947+
#[test]
948+
fn parses_invoice_with_relative_expiry() {
949+
let node_id = recipient_pubkey();
950+
let payment_paths = payment_paths();
951+
let now = now();
952+
let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
953+
let entropy = FixedEntropy {};
954+
let secp_ctx = Secp256k1::new();
955+
956+
let offer =
957+
OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, &entropy, &secp_ctx)
958+
.path(blinded_path())
959+
.build()
960+
.unwrap();
961+
962+
const TEST_RELATIVE_EXPIRY: u32 = 3600;
963+
let invoice = StaticInvoiceBuilder::for_offer_using_derived_keys(
964+
&offer,
965+
payment_paths.clone(),
966+
vec![blinded_path()],
967+
now,
968+
&expanded_key,
969+
&secp_ctx,
970+
)
971+
.unwrap()
972+
.relative_expiry(TEST_RELATIVE_EXPIRY)
973+
.build_and_sign(&secp_ctx)
974+
.unwrap();
975+
976+
let mut buffer = Vec::new();
977+
invoice.write(&mut buffer).unwrap();
978+
979+
match StaticInvoice::try_from(buffer) {
980+
Ok(invoice) => assert_eq!(
981+
invoice.relative_expiry(),
982+
Duration::from_secs(TEST_RELATIVE_EXPIRY as u64)
983+
),
984+
Err(e) => panic!("error parsing invoice: {:?}", e),
985+
}
986+
}
987+
988+
#[test]
989+
fn parses_invoice_with_allow_mpp() {
990+
let node_id = recipient_pubkey();
991+
let payment_paths = payment_paths();
992+
let now = now();
993+
let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
994+
let entropy = FixedEntropy {};
995+
let secp_ctx = Secp256k1::new();
996+
997+
let offer =
998+
OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, &entropy, &secp_ctx)
999+
.path(blinded_path())
1000+
.build()
1001+
.unwrap();
1002+
1003+
let invoice = StaticInvoiceBuilder::for_offer_using_derived_keys(
1004+
&offer,
1005+
payment_paths.clone(),
1006+
vec![blinded_path()],
1007+
now,
1008+
&expanded_key,
1009+
&secp_ctx,
1010+
)
1011+
.unwrap()
1012+
.allow_mpp()
1013+
.build_and_sign(&secp_ctx)
1014+
.unwrap();
1015+
1016+
let mut buffer = Vec::new();
1017+
invoice.write(&mut buffer).unwrap();
1018+
1019+
match StaticInvoice::try_from(buffer) {
1020+
Ok(invoice) => {
1021+
let mut features = Bolt12InvoiceFeatures::empty();
1022+
features.set_basic_mpp_optional();
1023+
assert_eq!(invoice.invoice_features(), &features);
1024+
},
1025+
Err(e) => panic!("error parsing invoice: {:?}", e),
1026+
}
1027+
}
1028+
1029+
#[test]
1030+
fn fails_parsing_missing_invoice_fields() {
1031+
// Error if `created_at` is missing.
1032+
let missing_created_at_invoice = invoice();
1033+
let mut tlv_stream = missing_created_at_invoice.as_tlv_stream();
1034+
tlv_stream.1.created_at = None;
1035+
match StaticInvoice::try_from(tlv_stream_to_bytes(&tlv_stream)) {
1036+
Ok(_) => panic!("expected error"),
1037+
Err(e) => {
1038+
assert_eq!(
1039+
e,
1040+
Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingCreationTime)
1041+
);
1042+
},
1043+
}
1044+
1045+
// Error if `node_id` is missing.
1046+
let missing_node_id_invoice = invoice();
1047+
let mut tlv_stream = missing_node_id_invoice.as_tlv_stream();
1048+
tlv_stream.1.node_id = None;
1049+
match StaticInvoice::try_from(tlv_stream_to_bytes(&tlv_stream)) {
1050+
Ok(_) => panic!("expected error"),
1051+
Err(e) => {
1052+
assert_eq!(
1053+
e,
1054+
Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingSigningPubkey)
1055+
);
1056+
},
1057+
}
1058+
1059+
// Error if message paths are missing.
1060+
let missing_message_paths_invoice = invoice();
1061+
let mut tlv_stream = missing_message_paths_invoice.as_tlv_stream();
1062+
tlv_stream.1.message_paths = None;
1063+
match StaticInvoice::try_from(tlv_stream_to_bytes(&tlv_stream)) {
1064+
Ok(_) => panic!("expected error"),
1065+
Err(e) => {
1066+
assert_eq!(
1067+
e,
1068+
Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingPaths)
1069+
);
1070+
},
1071+
}
1072+
1073+
// Error if signature is missing.
1074+
let invoice = invoice();
1075+
let mut buffer = Vec::new();
1076+
invoice.contents.as_tlv_stream().write(&mut buffer).unwrap();
1077+
match StaticInvoice::try_from(buffer) {
1078+
Ok(_) => panic!("expected error"),
1079+
Err(e) => assert_eq!(
1080+
e,
1081+
Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingSignature)
1082+
),
1083+
}
1084+
}
1085+
1086+
#[test]
1087+
fn fails_parsing_invalid_signing_pubkey() {
1088+
let invoice = invoice();
1089+
let invalid_pubkey = payer_pubkey();
1090+
let mut tlv_stream = invoice.as_tlv_stream();
1091+
tlv_stream.1.node_id = Some(&invalid_pubkey);
1092+
1093+
match StaticInvoice::try_from(tlv_stream_to_bytes(&tlv_stream)) {
1094+
Ok(_) => panic!("expected error"),
1095+
Err(e) => {
1096+
assert_eq!(
1097+
e,
1098+
Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::InvalidSigningPubkey)
1099+
);
1100+
},
1101+
}
1102+
}
1103+
1104+
#[test]
1105+
fn fails_parsing_invoice_with_invalid_signature() {
1106+
let mut invoice = invoice();
1107+
let last_signature_byte = invoice.bytes.last_mut().unwrap();
1108+
*last_signature_byte = last_signature_byte.wrapping_add(1);
1109+
1110+
let mut buffer = Vec::new();
1111+
invoice.write(&mut buffer).unwrap();
1112+
1113+
match StaticInvoice::try_from(buffer) {
1114+
Ok(_) => panic!("expected error"),
1115+
Err(e) => {
1116+
assert_eq!(
1117+
e,
1118+
Bolt12ParseError::InvalidSignature(secp256k1::Error::InvalidSignature)
1119+
);
1120+
},
1121+
}
1122+
}
1123+
1124+
#[test]
1125+
fn fails_parsing_invoice_with_extra_tlv_records() {
1126+
let invoice = invoice();
1127+
let mut encoded_invoice = Vec::new();
1128+
invoice.write(&mut encoded_invoice).unwrap();
1129+
BigSize(1002).write(&mut encoded_invoice).unwrap();
1130+
BigSize(32).write(&mut encoded_invoice).unwrap();
1131+
[42u8; 32].write(&mut encoded_invoice).unwrap();
1132+
1133+
match StaticInvoice::try_from(encoded_invoice) {
1134+
Ok(_) => panic!("expected error"),
1135+
Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::InvalidValue)),
1136+
}
1137+
}
1138+
1139+
#[test]
1140+
fn fails_parsing_invoice_with_invalid_offer_fields() {
1141+
// Error if the offer is missing paths.
1142+
let missing_offer_paths_invoice = invoice();
1143+
let mut tlv_stream = missing_offer_paths_invoice.as_tlv_stream();
1144+
tlv_stream.0.paths = None;
1145+
match StaticInvoice::try_from(tlv_stream_to_bytes(&tlv_stream)) {
1146+
Ok(_) => panic!("expected error"),
1147+
Err(e) => {
1148+
assert_eq!(
1149+
e,
1150+
Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingPaths)
1151+
);
1152+
},
1153+
}
1154+
1155+
// Error if the offer has more than one chain.
1156+
let invalid_offer_chains_invoice = invoice();
1157+
let mut tlv_stream = invalid_offer_chains_invoice.as_tlv_stream();
1158+
let invalid_chains = vec![
1159+
ChainHash::using_genesis_block(Network::Bitcoin),
1160+
ChainHash::using_genesis_block(Network::Testnet),
1161+
];
1162+
tlv_stream.0.chains = Some(&invalid_chains);
1163+
match StaticInvoice::try_from(tlv_stream_to_bytes(&tlv_stream)) {
1164+
Ok(_) => panic!("expected error"),
1165+
Err(e) => {
1166+
assert_eq!(
1167+
e,
1168+
Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::UnexpectedChain)
1169+
);
1170+
},
1171+
}
1172+
}
9081173
}

0 commit comments

Comments
 (0)