Skip to content

Commit 0e15083

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

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
@@ -178,6 +178,12 @@ impl<'a> InvoiceRequestBuilder<'a> {
178178
let InvoiceRequestBuilder { offer, invoice_request } = self;
179179
Ok(UnsignedInvoiceRequest { offer, invoice_request })
180180
}
181+
182+
#[cfg(test)]
183+
fn build_unchecked(self) -> UnsignedInvoiceRequest<'a> {
184+
let InvoiceRequestBuilder { offer, invoice_request } = self;
185+
UnsignedInvoiceRequest { offer, invoice_request }
186+
}
181187
}
182188

183189
/// A semantically valid [`InvoiceRequest`] that hasn't been signed.
@@ -463,15 +469,15 @@ mod tests {
463469

464470
use bitcoin::blockdata::constants::ChainHash;
465471
use bitcoin::network::constants::Network;
466-
use bitcoin::secp256k1::{KeyPair, Message, PublicKey, Secp256k1, SecretKey};
472+
use bitcoin::secp256k1::{KeyPair, Message, PublicKey, Secp256k1, SecretKey, self};
467473
use bitcoin::secp256k1::schnorr::Signature;
468474
use core::convert::TryFrom;
469475
use core::num::NonZeroU64;
470476
#[cfg(feature = "std")]
471477
use core::time::Duration;
472478
use crate::ln::features::InvoiceRequestFeatures;
473479
use crate::ln::msgs::DecodeError;
474-
use crate::offers::offer::{OfferBuilder, Quantity};
480+
use crate::offers::offer::{Amount, Offer, OfferBuilder, Quantity};
475481
use crate::offers::parse::{ParseError, SemanticError};
476482
use crate::util::ser::{BigSize, Writeable};
477483
use crate::util::string::PrintableString;
@@ -812,6 +818,318 @@ mod tests {
812818
assert_eq!(tlv_stream.payer_note, Some(&String::from("baz")));
813819
}
814820

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

384392
#[cfg(test)]
385-
fn as_tlv_stream(&self) -> OfferTlvStreamRef {
393+
pub(crate) fn as_tlv_stream(&self) -> OfferTlvStreamRef {
386394
self.contents.as_tlv_stream()
387395
}
388396
}

0 commit comments

Comments
 (0)