Skip to content

Commit d562ee8

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

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

184190
/// A semantically valid [`InvoiceRequest`] that hasn't been signed.
@@ -471,13 +477,13 @@ mod tests {
471477

472478
use bitcoin::blockdata::constants::ChainHash;
473479
use bitcoin::network::constants::Network;
474-
use bitcoin::secp256k1::{KeyPair, Message, PublicKey, Secp256k1, SecretKey};
480+
use bitcoin::secp256k1::{KeyPair, Message, PublicKey, Secp256k1, SecretKey, self};
475481
use bitcoin::secp256k1::schnorr::Signature;
476482
use core::convert::TryFrom;
477483
use core::num::NonZeroU64;
478484
use crate::ln::features::InvoiceRequestFeatures;
479485
use crate::ln::msgs::DecodeError;
480-
use crate::offers::offer::{OfferBuilder, Quantity};
486+
use crate::offers::offer::{Amount, Offer, OfferBuilder, Quantity};
481487
use crate::offers::parse::{ParseError, SemanticError};
482488
use crate::util::ser::{BigSize, Writeable};
483489
use crate::util::string::PrintableString;
@@ -792,6 +798,318 @@ mod tests {
792798
assert_eq!(tlv_stream.payer_note, Some(&String::from("baz")));
793799
}
794800

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