Skip to content

Commit 391bcbe

Browse files
Static invoice parsing tests
1 parent 555b9c0 commit 391bcbe

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
@@ -567,19 +567,20 @@ mod tests {
567567
use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode};
568568
use crate::ln::features::{Bolt12InvoiceFeatures, OfferFeatures};
569569
use crate::ln::inbound_payment::ExpandedKey;
570+
use crate::ln::msgs::DecodeError;
570571
use crate::offers::invoice::InvoiceTlvStreamRef;
571572
use crate::offers::merkle;
572573
use crate::offers::merkle::{SignatureTlvStreamRef, TaggedHash};
573574
use crate::offers::offer::{Offer, OfferBuilder, OfferTlvStreamRef, Quantity};
574-
use crate::offers::parse::Bolt12SemanticError;
575+
use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError};
575576
use crate::offers::static_invoice::{
576577
StaticInvoice, StaticInvoiceBuilder, DEFAULT_RELATIVE_EXPIRY, SIGNATURE_TAG,
577578
};
578579
use crate::offers::test_utils::*;
579580
use crate::sign::KeyMaterial;
580-
use crate::util::ser::{Iterable, Writeable};
581+
use crate::util::ser::{BigSize, Iterable, Writeable};
581582
use bitcoin::blockdata::constants::ChainHash;
582-
use bitcoin::secp256k1::Secp256k1;
583+
use bitcoin::secp256k1::{self, Secp256k1};
583584
use bitcoin::Network;
584585
use core::time::Duration;
585586

@@ -597,6 +598,43 @@ mod tests {
597598
}
598599
}
599600

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

0 commit comments

Comments
 (0)