Skip to content

Commit 4d4af16

Browse files
committed
Invoice request parsing tests
Tests for checking invoice_request message semantics when parsing bytes as defined by BOLT 12.
1 parent 562c90d commit 4d4af16

File tree

2 files changed

+329
-3
lines changed

2 files changed

+329
-3
lines changed

lightning/src/offers/invoice_request.rs

Lines changed: 319 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,12 @@ impl<'a> InvoiceRequestBuilder<'a> {
182182
let InvoiceRequestBuilder { offer, invoice_request } = self;
183183
Ok(UnsignedInvoiceRequest { offer, invoice_request })
184184
}
185+
186+
#[cfg(test)]
187+
fn build_unchecked(self) -> UnsignedInvoiceRequest<'a> {
188+
let InvoiceRequestBuilder { offer, invoice_request } = self;
189+
UnsignedInvoiceRequest { offer, invoice_request }
190+
}
185191
}
186192

187193
/// A semantically valid [`InvoiceRequest`] that hasn't been signed.
@@ -468,7 +474,7 @@ mod tests {
468474
use crate::ln::features::InvoiceRequestFeatures;
469475
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
470476
use crate::offers::merkle::SignError;
471-
use crate::offers::offer::{OfferBuilder, Quantity};
477+
use crate::offers::offer::{Amount, Offer, OfferBuilder, Quantity};
472478
use crate::offers::parse::{ParseError, SemanticError};
473479
use crate::util::ser::{BigSize, Writeable};
474480
use crate::util::string::PrintableString;
@@ -872,6 +878,318 @@ mod tests {
872878
}
873879
}
874880

881+
#[test]
882+
fn parses_invoice_request_with_metadata() {
883+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
884+
.amount_msats(1000)
885+
.build().unwrap()
886+
.request_invoice(payer_pubkey())
887+
.metadata(vec![42; 32])
888+
.build().unwrap()
889+
.sign(payer_sign).unwrap();
890+
891+
let mut buffer = Vec::new();
892+
invoice_request.write(&mut buffer).unwrap();
893+
894+
if let Err(e) = InvoiceRequest::try_from(buffer) {
895+
panic!("error parsing invoice_request: {:?}", e);
896+
}
897+
}
898+
899+
#[test]
900+
fn parses_invoice_request_with_chain() {
901+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
902+
.amount_msats(1000)
903+
.build().unwrap()
904+
.request_invoice(payer_pubkey())
905+
.chain(Network::Bitcoin)
906+
.build().unwrap()
907+
.sign(payer_sign).unwrap();
908+
909+
let mut buffer = Vec::new();
910+
invoice_request.write(&mut buffer).unwrap();
911+
912+
if let Err(e) = InvoiceRequest::try_from(buffer) {
913+
panic!("error parsing invoice_request: {:?}", e);
914+
}
915+
916+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
917+
.amount_msats(1000)
918+
.build().unwrap()
919+
.request_invoice(payer_pubkey())
920+
.chain(Network::Testnet)
921+
.build_unchecked()
922+
.sign(payer_sign).unwrap();
923+
924+
let mut buffer = Vec::new();
925+
invoice_request.write(&mut buffer).unwrap();
926+
927+
match InvoiceRequest::try_from(buffer) {
928+
Ok(_) => panic!("expected error"),
929+
Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnsupportedChain)),
930+
}
931+
}
932+
933+
#[test]
934+
fn parses_invoice_request_with_amount() {
935+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
936+
.amount_msats(1000)
937+
.build().unwrap()
938+
.request_invoice(payer_pubkey())
939+
.build().unwrap()
940+
.sign(payer_sign).unwrap();
941+
942+
let mut buffer = Vec::new();
943+
invoice_request.write(&mut buffer).unwrap();
944+
945+
if let Err(e) = InvoiceRequest::try_from(buffer) {
946+
panic!("error parsing invoice_request: {:?}", e);
947+
}
948+
949+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
950+
.build().unwrap()
951+
.request_invoice(payer_pubkey())
952+
.amount_msats(1000)
953+
.build().unwrap()
954+
.sign(payer_sign).unwrap();
955+
956+
let mut buffer = Vec::new();
957+
invoice_request.write(&mut buffer).unwrap();
958+
959+
if let Err(e) = InvoiceRequest::try_from(buffer) {
960+
panic!("error parsing invoice_request: {:?}", e);
961+
}
962+
963+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
964+
.build().unwrap()
965+
.request_invoice(payer_pubkey())
966+
.build_unchecked()
967+
.sign(payer_sign).unwrap();
968+
969+
let mut buffer = Vec::new();
970+
invoice_request.write(&mut buffer).unwrap();
971+
972+
match InvoiceRequest::try_from(buffer) {
973+
Ok(_) => panic!("expected error"),
974+
Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingAmount)),
975+
}
976+
977+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
978+
.amount_msats(1000)
979+
.build().unwrap()
980+
.request_invoice(payer_pubkey())
981+
.amount_msats(999)
982+
.build_unchecked()
983+
.sign(payer_sign).unwrap();
984+
985+
let mut buffer = Vec::new();
986+
invoice_request.write(&mut buffer).unwrap();
987+
988+
match InvoiceRequest::try_from(buffer) {
989+
Ok(_) => panic!("expected error"),
990+
Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InsufficientAmount)),
991+
}
992+
993+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
994+
.amount(Amount::Currency { iso4217_code: *b"USD", amount: 1000 })
995+
.build_unchecked()
996+
.request_invoice(payer_pubkey())
997+
.build_unchecked()
998+
.sign(payer_sign).unwrap();
999+
1000+
let mut buffer = Vec::new();
1001+
invoice_request.write(&mut buffer).unwrap();
1002+
1003+
match InvoiceRequest::try_from(buffer) {
1004+
Ok(_) => panic!("expected error"),
1005+
Err(e) => {
1006+
assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnsupportedCurrency));
1007+
},
1008+
}
1009+
}
1010+
1011+
#[test]
1012+
fn parses_invoice_request_with_quantity() {
1013+
let ten = NonZeroU64::new(10).unwrap();
1014+
1015+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
1016+
.amount_msats(1000)
1017+
.supported_quantity(Quantity::one())
1018+
.build().unwrap()
1019+
.request_invoice(payer_pubkey())
1020+
.build().unwrap()
1021+
.sign(payer_sign).unwrap();
1022+
1023+
let mut buffer = Vec::new();
1024+
invoice_request.write(&mut buffer).unwrap();
1025+
1026+
if let Err(e) = InvoiceRequest::try_from(buffer) {
1027+
panic!("error parsing invoice_request: {:?}", e);
1028+
}
1029+
1030+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
1031+
.amount_msats(1000)
1032+
.supported_quantity(Quantity::one())
1033+
.build().unwrap()
1034+
.request_invoice(payer_pubkey())
1035+
.amount_msats(2_000)
1036+
.quantity(2)
1037+
.build_unchecked()
1038+
.sign(payer_sign).unwrap();
1039+
1040+
let mut buffer = Vec::new();
1041+
invoice_request.write(&mut buffer).unwrap();
1042+
1043+
match InvoiceRequest::try_from(buffer) {
1044+
Ok(_) => panic!("expected error"),
1045+
Err(e) => {
1046+
assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnexpectedQuantity));
1047+
},
1048+
}
1049+
1050+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
1051+
.amount_msats(1000)
1052+
.supported_quantity(Quantity::Bounded(ten))
1053+
.build().unwrap()
1054+
.request_invoice(payer_pubkey())
1055+
.amount_msats(10_000)
1056+
.quantity(10)
1057+
.build().unwrap()
1058+
.sign(payer_sign).unwrap();
1059+
1060+
let mut buffer = Vec::new();
1061+
invoice_request.write(&mut buffer).unwrap();
1062+
1063+
if let Err(e) = InvoiceRequest::try_from(buffer) {
1064+
panic!("error parsing invoice_request: {:?}", e);
1065+
}
1066+
1067+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
1068+
.amount_msats(1000)
1069+
.supported_quantity(Quantity::Bounded(ten))
1070+
.build().unwrap()
1071+
.request_invoice(payer_pubkey())
1072+
.amount_msats(11_000)
1073+
.quantity(11)
1074+
.build_unchecked()
1075+
.sign(payer_sign).unwrap();
1076+
1077+
let mut buffer = Vec::new();
1078+
invoice_request.write(&mut buffer).unwrap();
1079+
1080+
match InvoiceRequest::try_from(buffer) {
1081+
Ok(_) => panic!("expected error"),
1082+
Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InvalidQuantity)),
1083+
}
1084+
1085+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
1086+
.amount_msats(1000)
1087+
.supported_quantity(Quantity::Unbounded)
1088+
.build().unwrap()
1089+
.request_invoice(payer_pubkey())
1090+
.amount_msats(2_000)
1091+
.quantity(2)
1092+
.build().unwrap()
1093+
.sign(payer_sign).unwrap();
1094+
1095+
let mut buffer = Vec::new();
1096+
invoice_request.write(&mut buffer).unwrap();
1097+
1098+
if let Err(e) = InvoiceRequest::try_from(buffer) {
1099+
panic!("error parsing invoice_request: {:?}", e);
1100+
}
1101+
1102+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
1103+
.amount_msats(1000)
1104+
.supported_quantity(Quantity::Unbounded)
1105+
.build().unwrap()
1106+
.request_invoice(payer_pubkey())
1107+
.build_unchecked()
1108+
.sign(payer_sign).unwrap();
1109+
1110+
let mut buffer = Vec::new();
1111+
invoice_request.write(&mut buffer).unwrap();
1112+
1113+
match InvoiceRequest::try_from(buffer) {
1114+
Ok(_) => panic!("expected error"),
1115+
Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingQuantity)),
1116+
}
1117+
}
1118+
1119+
#[test]
1120+
fn parses_invoice_request_with_payer_id() {
1121+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
1122+
.amount_msats(1000)
1123+
.build().unwrap()
1124+
.request_invoice(payer_pubkey())
1125+
.build().unwrap()
1126+
.sign(payer_sign).unwrap();
1127+
1128+
let mut buffer = Vec::new();
1129+
invoice_request.write(&mut buffer).unwrap();
1130+
1131+
if let Err(e) = InvoiceRequest::try_from(buffer) {
1132+
panic!("error parsing invoice_request: {:?}", e);
1133+
}
1134+
1135+
let offer = OfferBuilder::new("foo".into(), recipient_pubkey())
1136+
.amount_msats(1000)
1137+
.build().unwrap();
1138+
let mut unsigned_invoice_request = offer.request_invoice(payer_pubkey()).build().unwrap();
1139+
let mut tlv_stream = unsigned_invoice_request.invoice_request.as_tlv_stream();
1140+
tlv_stream.2.payer_id = None;
1141+
1142+
let mut buffer = Vec::new();
1143+
tlv_stream.write(&mut buffer).unwrap();
1144+
1145+
match InvoiceRequest::try_from(buffer) {
1146+
Ok(_) => panic!("expected error"),
1147+
Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingPayerId)),
1148+
}
1149+
}
1150+
1151+
#[test]
1152+
fn fails_parsing_invoice_request_with_missing_node_id() {
1153+
let offer = OfferBuilder::new("foo".into(), recipient_pubkey())
1154+
.amount_msats(1000)
1155+
.build().unwrap();
1156+
let mut unsigned_invoice_request = offer.request_invoice(payer_pubkey()).build().unwrap();
1157+
let mut tlv_stream = unsigned_invoice_request.invoice_request.as_tlv_stream();
1158+
tlv_stream.1.node_id = None;
1159+
1160+
let mut buffer = Vec::new();
1161+
tlv_stream.write(&mut buffer).unwrap();
1162+
1163+
match InvoiceRequest::try_from(buffer) {
1164+
Ok(_) => panic!("expected error"),
1165+
Err(e) => {
1166+
assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingSigningPubkey));
1167+
},
1168+
}
1169+
}
1170+
1171+
#[test]
1172+
fn fails_parsing_invoice_request_with_invalid_signature() {
1173+
let mut invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
1174+
.amount_msats(1000)
1175+
.build().unwrap()
1176+
.request_invoice(payer_pubkey())
1177+
.build().unwrap()
1178+
.sign(payer_sign).unwrap();
1179+
let last_signature_byte = invoice_request.bytes.last_mut().unwrap();
1180+
*last_signature_byte = last_signature_byte.wrapping_add(1);
1181+
1182+
let mut buffer = Vec::new();
1183+
invoice_request.write(&mut buffer).unwrap();
1184+
1185+
match InvoiceRequest::try_from(buffer) {
1186+
Ok(_) => panic!("expected error"),
1187+
Err(e) => {
1188+
assert_eq!(e, ParseError::InvalidSignature(secp256k1::Error::InvalidSignature));
1189+
},
1190+
}
1191+
}
1192+
8751193
#[test]
8761194
fn fails_parsing_invoice_request_with_extra_tlv_records() {
8771195
let secp_ctx = Secp256k1::new();

lightning/src/offers/offer.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ impl OfferBuilder {
145145
/// Sets the [`Offer::amount`].
146146
///
147147
/// Successive calls to this method will override the previous setting.
148-
fn amount(mut self, amount: Amount) -> Self {
148+
pub(crate) fn amount(mut self, amount: Amount) -> Self {
149149
self.offer.amount = Some(amount);
150150
self
151151
}
@@ -221,6 +221,14 @@ impl OfferBuilder {
221221
contents: self.offer,
222222
})
223223
}
224+
225+
#[cfg(test)]
226+
pub(crate) fn build_unchecked(self) -> Offer {
227+
let mut bytes = Vec::new();
228+
self.offer.write(&mut bytes).unwrap();
229+
230+
Offer { bytes, contents: self.offer }
231+
}
224232
}
225233

226234
/// An `Offer` is a potentially long-lived proposal for payment of a good or service.
@@ -372,7 +380,7 @@ impl Offer {
372380
}
373381

374382
#[cfg(test)]
375-
fn as_tlv_stream(&self) -> OfferTlvStreamRef {
383+
pub(crate) fn as_tlv_stream(&self) -> OfferTlvStreamRef {
376384
self.contents.as_tlv_stream()
377385
}
378386
}

0 commit comments

Comments
 (0)