Skip to content

Commit ace769c

Browse files
committed
Invoice request parsing tests
Tests for checking invoice_request message semantics when parsing bytes as defined by BOLT 12.
1 parent 84059d3 commit ace769c

File tree

2 files changed

+330
-4
lines changed

2 files changed

+330
-4
lines changed

lightning/src/offers/invoice_request.rs

Lines changed: 320 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,12 @@ impl<'a> InvoiceRequestBuilder<'a> {
194194
let InvoiceRequestBuilder { offer, invoice_request } = self;
195195
Ok(UnsignedInvoiceRequest { offer, invoice_request })
196196
}
197+
198+
#[cfg(test)]
199+
fn build_unchecked(self) -> UnsignedInvoiceRequest<'a> {
200+
let InvoiceRequestBuilder { offer, invoice_request } = self;
201+
UnsignedInvoiceRequest { offer, invoice_request }
202+
}
197203
}
198204

199205
/// A semantically valid [`InvoiceRequest`] that hasn't been signed.
@@ -483,13 +489,13 @@ mod tests {
483489

484490
use bitcoin::blockdata::constants::ChainHash;
485491
use bitcoin::network::constants::Network;
486-
use bitcoin::secp256k1::{KeyPair, Message, PublicKey, Secp256k1, SecretKey};
492+
use bitcoin::secp256k1::{KeyPair, Message, PublicKey, Secp256k1, SecretKey, self};
487493
use bitcoin::secp256k1::schnorr::Signature;
488494
use core::convert::TryFrom;
489495
use core::num::NonZeroU64;
490496
use crate::ln::features::InvoiceRequestFeatures;
491497
use crate::ln::msgs::DecodeError;
492-
use crate::offers::offer::{OfferBuilder, Quantity};
498+
use crate::offers::offer::{Amount, Offer, OfferBuilder, Quantity};
493499
use crate::offers::parse::{ParseError, SemanticError};
494500
use crate::util::ser::{BigSize, Writeable};
495501
use crate::util::string::PrintableString;
@@ -828,6 +834,318 @@ mod tests {
828834
assert_eq!(tlv_stream.payer_note, Some(&String::from("baz")));
829835
}
830836

837+
#[test]
838+
fn parses_invoice_request_with_metadata() {
839+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
840+
.amount_msats(1000)
841+
.build().unwrap()
842+
.request_invoice(payer_pubkey())
843+
.metadata(vec![42; 32])
844+
.build().unwrap()
845+
.sign(payer_sign).unwrap();
846+
847+
let mut buffer = Vec::new();
848+
invoice_request.write(&mut buffer).unwrap();
849+
850+
if let Err(e) = InvoiceRequest::try_from(buffer) {
851+
panic!("error parsing invoice_request: {:?}", e);
852+
}
853+
}
854+
855+
#[test]
856+
fn parses_invoice_request_with_chain() {
857+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
858+
.amount_msats(1000)
859+
.build().unwrap()
860+
.request_invoice(payer_pubkey())
861+
.chain(Network::Bitcoin)
862+
.build().unwrap()
863+
.sign(payer_sign).unwrap();
864+
865+
let mut buffer = Vec::new();
866+
invoice_request.write(&mut buffer).unwrap();
867+
868+
if let Err(e) = InvoiceRequest::try_from(buffer) {
869+
panic!("error parsing invoice_request: {:?}", e);
870+
}
871+
872+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
873+
.amount_msats(1000)
874+
.build().unwrap()
875+
.request_invoice(payer_pubkey())
876+
.chain(Network::Testnet)
877+
.build_unchecked()
878+
.sign(payer_sign).unwrap();
879+
880+
let mut buffer = Vec::new();
881+
invoice_request.write(&mut buffer).unwrap();
882+
883+
match InvoiceRequest::try_from(buffer) {
884+
Ok(_) => panic!("expected error"),
885+
Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnsupportedChain)),
886+
}
887+
}
888+
889+
#[test]
890+
fn parses_invoice_request_with_amount() {
891+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
892+
.amount_msats(1000)
893+
.build().unwrap()
894+
.request_invoice(payer_pubkey())
895+
.build().unwrap()
896+
.sign(payer_sign).unwrap();
897+
898+
let mut buffer = Vec::new();
899+
invoice_request.write(&mut buffer).unwrap();
900+
901+
if let Err(e) = InvoiceRequest::try_from(buffer) {
902+
panic!("error parsing invoice_request: {:?}", e);
903+
}
904+
905+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
906+
.build().unwrap()
907+
.request_invoice(payer_pubkey())
908+
.amount_msats(1000)
909+
.build().unwrap()
910+
.sign(payer_sign).unwrap();
911+
912+
let mut buffer = Vec::new();
913+
invoice_request.write(&mut buffer).unwrap();
914+
915+
if let Err(e) = InvoiceRequest::try_from(buffer) {
916+
panic!("error parsing invoice_request: {:?}", e);
917+
}
918+
919+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
920+
.build().unwrap()
921+
.request_invoice(payer_pubkey())
922+
.build_unchecked()
923+
.sign(payer_sign).unwrap();
924+
925+
let mut buffer = Vec::new();
926+
invoice_request.write(&mut buffer).unwrap();
927+
928+
match InvoiceRequest::try_from(buffer) {
929+
Ok(_) => panic!("expected error"),
930+
Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingAmount)),
931+
}
932+
933+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
934+
.amount_msats(1000)
935+
.build().unwrap()
936+
.request_invoice(payer_pubkey())
937+
.amount_msats(999)
938+
.build_unchecked()
939+
.sign(payer_sign).unwrap();
940+
941+
let mut buffer = Vec::new();
942+
invoice_request.write(&mut buffer).unwrap();
943+
944+
match InvoiceRequest::try_from(buffer) {
945+
Ok(_) => panic!("expected error"),
946+
Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InsufficientAmount)),
947+
}
948+
949+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
950+
.amount(Amount::Currency { iso4217_code: *b"USD", amount: 1000 })
951+
.build_unchecked()
952+
.request_invoice(payer_pubkey())
953+
.build_unchecked()
954+
.sign(payer_sign).unwrap();
955+
956+
let mut buffer = Vec::new();
957+
invoice_request.write(&mut buffer).unwrap();
958+
959+
match InvoiceRequest::try_from(buffer) {
960+
Ok(_) => panic!("expected error"),
961+
Err(e) => {
962+
assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnsupportedCurrency));
963+
},
964+
}
965+
}
966+
967+
#[test]
968+
fn parses_invoice_request_with_quantity() {
969+
let ten = NonZeroU64::new(10).unwrap();
970+
971+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
972+
.amount_msats(1000)
973+
.supported_quantity(Quantity::one())
974+
.build().unwrap()
975+
.request_invoice(payer_pubkey())
976+
.build().unwrap()
977+
.sign(payer_sign).unwrap();
978+
979+
let mut buffer = Vec::new();
980+
invoice_request.write(&mut buffer).unwrap();
981+
982+
if let Err(e) = InvoiceRequest::try_from(buffer) {
983+
panic!("error parsing invoice_request: {:?}", e);
984+
}
985+
986+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
987+
.amount_msats(1000)
988+
.supported_quantity(Quantity::one())
989+
.build().unwrap()
990+
.request_invoice(payer_pubkey())
991+
.amount_msats(2_000)
992+
.quantity(2)
993+
.build_unchecked()
994+
.sign(payer_sign).unwrap();
995+
996+
let mut buffer = Vec::new();
997+
invoice_request.write(&mut buffer).unwrap();
998+
999+
match InvoiceRequest::try_from(buffer) {
1000+
Ok(_) => panic!("expected error"),
1001+
Err(e) => {
1002+
assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnexpectedQuantity));
1003+
},
1004+
}
1005+
1006+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
1007+
.amount_msats(1000)
1008+
.supported_quantity(Quantity::Bounded(ten))
1009+
.build().unwrap()
1010+
.request_invoice(payer_pubkey())
1011+
.amount_msats(10_000)
1012+
.quantity(10)
1013+
.build().unwrap()
1014+
.sign(payer_sign).unwrap();
1015+
1016+
let mut buffer = Vec::new();
1017+
invoice_request.write(&mut buffer).unwrap();
1018+
1019+
if let Err(e) = InvoiceRequest::try_from(buffer) {
1020+
panic!("error parsing invoice_request: {:?}", e);
1021+
}
1022+
1023+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
1024+
.amount_msats(1000)
1025+
.supported_quantity(Quantity::Bounded(ten))
1026+
.build().unwrap()
1027+
.request_invoice(payer_pubkey())
1028+
.amount_msats(11_000)
1029+
.quantity(11)
1030+
.build_unchecked()
1031+
.sign(payer_sign).unwrap();
1032+
1033+
let mut buffer = Vec::new();
1034+
invoice_request.write(&mut buffer).unwrap();
1035+
1036+
match InvoiceRequest::try_from(buffer) {
1037+
Ok(_) => panic!("expected error"),
1038+
Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InvalidQuantity)),
1039+
}
1040+
1041+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
1042+
.amount_msats(1000)
1043+
.supported_quantity(Quantity::Unbounded)
1044+
.build().unwrap()
1045+
.request_invoice(payer_pubkey())
1046+
.amount_msats(2_000)
1047+
.quantity(2)
1048+
.build().unwrap()
1049+
.sign(payer_sign).unwrap();
1050+
1051+
let mut buffer = Vec::new();
1052+
invoice_request.write(&mut buffer).unwrap();
1053+
1054+
if let Err(e) = InvoiceRequest::try_from(buffer) {
1055+
panic!("error parsing invoice_request: {:?}", e);
1056+
}
1057+
1058+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
1059+
.amount_msats(1000)
1060+
.supported_quantity(Quantity::Unbounded)
1061+
.build().unwrap()
1062+
.request_invoice(payer_pubkey())
1063+
.build_unchecked()
1064+
.sign(payer_sign).unwrap();
1065+
1066+
let mut buffer = Vec::new();
1067+
invoice_request.write(&mut buffer).unwrap();
1068+
1069+
match InvoiceRequest::try_from(buffer) {
1070+
Ok(_) => panic!("expected error"),
1071+
Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingQuantity)),
1072+
}
1073+
}
1074+
1075+
#[test]
1076+
fn parses_invoice_request_with_payer_id() {
1077+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
1078+
.amount_msats(1000)
1079+
.build().unwrap()
1080+
.request_invoice(payer_pubkey())
1081+
.build().unwrap()
1082+
.sign(payer_sign).unwrap();
1083+
1084+
let mut buffer = Vec::new();
1085+
invoice_request.write(&mut buffer).unwrap();
1086+
1087+
if let Err(e) = InvoiceRequest::try_from(buffer) {
1088+
panic!("error parsing invoice_request: {:?}", e);
1089+
}
1090+
1091+
let offer = OfferBuilder::new("foo".into(), recipient_pubkey())
1092+
.amount_msats(1000)
1093+
.build().unwrap();
1094+
let mut unsigned_invoice_request = offer.request_invoice(payer_pubkey()).build().unwrap();
1095+
let mut tlv_stream = unsigned_invoice_request.invoice_request.as_tlv_stream();
1096+
tlv_stream.2.payer_id = None;
1097+
1098+
let mut buffer = Vec::new();
1099+
tlv_stream.write(&mut buffer).unwrap();
1100+
1101+
match InvoiceRequest::try_from(buffer) {
1102+
Ok(_) => panic!("expected error"),
1103+
Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingPayerId)),
1104+
}
1105+
}
1106+
1107+
#[test]
1108+
fn fails_parsing_invoice_request_with_missing_node_id() {
1109+
let offer = OfferBuilder::new("foo".into(), recipient_pubkey())
1110+
.amount_msats(1000)
1111+
.build().unwrap();
1112+
let mut unsigned_invoice_request = offer.request_invoice(payer_pubkey()).build().unwrap();
1113+
let mut tlv_stream = unsigned_invoice_request.invoice_request.as_tlv_stream();
1114+
tlv_stream.1.node_id = None;
1115+
1116+
let mut buffer = Vec::new();
1117+
tlv_stream.write(&mut buffer).unwrap();
1118+
1119+
match InvoiceRequest::try_from(buffer) {
1120+
Ok(_) => panic!("expected error"),
1121+
Err(e) => {
1122+
assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingSigningPubkey));
1123+
},
1124+
}
1125+
}
1126+
1127+
#[test]
1128+
fn fails_parsing_invoice_request_with_invalid_signature() {
1129+
let mut invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
1130+
.amount_msats(1000)
1131+
.build().unwrap()
1132+
.request_invoice(payer_pubkey())
1133+
.build().unwrap()
1134+
.sign(payer_sign).unwrap();
1135+
let last_signature_byte = invoice_request.bytes.last_mut().unwrap();
1136+
*last_signature_byte = last_signature_byte.wrapping_add(1);
1137+
1138+
let mut buffer = Vec::new();
1139+
invoice_request.write(&mut buffer).unwrap();
1140+
1141+
match InvoiceRequest::try_from(buffer) {
1142+
Ok(_) => panic!("expected error"),
1143+
Err(e) => {
1144+
assert_eq!(e, ParseError::InvalidSignature(secp256k1::Error::InvalidSignature));
1145+
},
1146+
}
1147+
}
1148+
8311149
#[test]
8321150
fn fails_parsing_invoice_request_with_extra_tlv_records() {
8331151
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.
@@ -374,7 +382,7 @@ impl Offer {
374382
}
375383

376384
#[cfg(test)]
377-
fn as_tlv_stream(&self) -> OfferTlvStreamRef {
385+
pub(crate) fn as_tlv_stream(&self) -> OfferTlvStreamRef {
378386
self.contents.as_tlv_stream()
379387
}
380388
}

0 commit comments

Comments
 (0)