Skip to content

Commit bcf17f2

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

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
@@ -189,6 +189,12 @@ impl<'a> InvoiceRequestBuilder<'a> {
189189
let InvoiceRequestBuilder { offer, invoice_request } = self;
190190
Ok(UnsignedInvoiceRequest { offer, invoice_request })
191191
}
192+
193+
#[cfg(test)]
194+
fn build_unchecked(self) -> UnsignedInvoiceRequest<'a> {
195+
let InvoiceRequestBuilder { offer, invoice_request } = self;
196+
UnsignedInvoiceRequest { offer, invoice_request }
197+
}
192198
}
193199

194200
/// A semantically valid [`InvoiceRequest`] that hasn't been signed.
@@ -478,13 +484,13 @@ mod tests {
478484

479485
use bitcoin::blockdata::constants::ChainHash;
480486
use bitcoin::network::constants::Network;
481-
use bitcoin::secp256k1::{KeyPair, Message, PublicKey, Secp256k1, SecretKey};
487+
use bitcoin::secp256k1::{KeyPair, Message, PublicKey, Secp256k1, SecretKey, self};
482488
use bitcoin::secp256k1::schnorr::Signature;
483489
use core::convert::TryFrom;
484490
use core::num::NonZeroU64;
485491
use crate::ln::features::InvoiceRequestFeatures;
486492
use crate::ln::msgs::DecodeError;
487-
use crate::offers::offer::{OfferBuilder, Quantity};
493+
use crate::offers::offer::{Amount, Offer, OfferBuilder, Quantity};
488494
use crate::offers::parse::{ParseError, SemanticError};
489495
use crate::util::ser::{BigSize, Writeable};
490496
use crate::util::string::PrintableString;
@@ -823,6 +829,318 @@ mod tests {
823829
assert_eq!(tlv_stream.payer_note, Some(&String::from("baz")));
824830
}
825831

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