Skip to content

Commit bafe4ed

Browse files
Static invoice parsing tests
1 parent 7970de4 commit bafe4ed

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
@@ -563,19 +563,20 @@ mod tests {
563563
use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode};
564564
use crate::ln::features::{Bolt12InvoiceFeatures, OfferFeatures};
565565
use crate::ln::inbound_payment::ExpandedKey;
566+
use crate::ln::msgs::DecodeError;
566567
use crate::offers::invoice::InvoiceTlvStreamRef;
567568
use crate::offers::merkle;
568569
use crate::offers::merkle::{SignatureTlvStreamRef, TaggedHash};
569570
use crate::offers::offer::{Offer, OfferBuilder, OfferTlvStreamRef, Quantity};
570-
use crate::offers::parse::Bolt12SemanticError;
571+
use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError};
571572
use crate::offers::static_invoice::{
572573
StaticInvoice, StaticInvoiceBuilder, DEFAULT_RELATIVE_EXPIRY, SIGNATURE_TAG,
573574
};
574575
use crate::offers::test_utils::*;
575576
use crate::sign::KeyMaterial;
576-
use crate::util::ser::{Iterable, Writeable};
577+
use crate::util::ser::{BigSize, Iterable, Writeable};
577578
use bitcoin::blockdata::constants::ChainHash;
578-
use bitcoin::secp256k1::Secp256k1;
579+
use bitcoin::secp256k1::{self, Secp256k1};
579580
use bitcoin::Network;
580581
use core::time::Duration;
581582

@@ -593,6 +594,43 @@ mod tests {
593594
}
594595
}
595596

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

0 commit comments

Comments
 (0)