Skip to content

Commit c97748a

Browse files
committed
Invoice parsing tests
Tests for checking invoice semantics when parsing invoice bytes as defined by BOLT 12.
1 parent b5b4a75 commit c97748a

File tree

2 files changed

+286
-3
lines changed

2 files changed

+286
-3
lines changed

lightning/src/offers/invoice.rs

+268-3
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,12 @@ impl Writeable for Invoice {
450450
}
451451
}
452452

453+
impl Writeable for InvoiceContents {
454+
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
455+
self.as_tlv_stream().write(writer)
456+
}
457+
}
458+
453459
impl TryFrom<Vec<u8>> for Invoice {
454460
type Error = ParseError;
455461

@@ -644,7 +650,7 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
644650

645651
#[cfg(test)]
646652
mod tests {
647-
use super::{DEFAULT_RELATIVE_EXPIRY, BlindedPayInfo, FallbackAddress, Invoice, InvoiceTlvStreamRef, SIGNATURE_TAG};
653+
use super::{DEFAULT_RELATIVE_EXPIRY, BlindedPayInfo, FallbackAddress, FullInvoiceTlvStreamRef, Invoice, InvoiceTlvStreamRef, SIGNATURE_TAG};
648654

649655
use bitcoin::blockdata::script::Script;
650656
use bitcoin::hashes::Hash;
@@ -657,15 +663,16 @@ mod tests {
657663
#[cfg(feature = "std")]
658664
use core::time::Duration;
659665
use crate::ln::PaymentHash;
666+
use crate::ln::msgs::DecodeError;
660667
use crate::ln::features::{BlindedHopFeatures, Bolt12InvoiceFeatures};
661668
use crate::offers::invoice_request::InvoiceRequestTlvStreamRef;
662669
use crate::offers::merkle::{SignError, SignatureTlvStreamRef, self};
663670
use crate::offers::offer::{OfferBuilder, OfferTlvStreamRef};
664-
use crate::offers::parse::SemanticError;
671+
use crate::offers::parse::{ParseError, SemanticError};
665672
use crate::offers::payer::PayerTlvStreamRef;
666673
use crate::offers::refund::RefundBuilder;
667674
use crate::onion_message::{BlindedHop, BlindedPath};
668-
use crate::util::ser::{Iterable, Writeable};
675+
use crate::util::ser::{BigSize, Iterable, Writeable};
669676

670677
fn payer_keys() -> KeyPair {
671678
let secp_ctx = Secp256k1::new();
@@ -706,6 +713,22 @@ mod tests {
706713
SecretKey::from_slice(&[byte; 32]).unwrap()
707714
}
708715

716+
trait ToBytes {
717+
fn to_bytes(&self) -> Vec<u8>;
718+
}
719+
720+
impl<'a> ToBytes for FullInvoiceTlvStreamRef<'a> {
721+
fn to_bytes(&self) -> Vec<u8> {
722+
let mut buffer = Vec::new();
723+
self.0.write(&mut buffer).unwrap();
724+
self.1.write(&mut buffer).unwrap();
725+
self.2.write(&mut buffer).unwrap();
726+
self.3.write(&mut buffer).unwrap();
727+
self.4.write(&mut buffer).unwrap();
728+
buffer
729+
}
730+
}
731+
709732
fn payment_paths() -> Vec<(BlindedPath, BlindedPayInfo)> {
710733
let paths = vec![
711734
BlindedPath {
@@ -1092,4 +1115,246 @@ mod tests {
10921115
Err(e) => assert_eq!(e, SignError::Verification(secp256k1::Error::InvalidSignature)),
10931116
}
10941117
}
1118+
1119+
#[test]
1120+
fn parses_invoice_with_payment_paths() {
1121+
let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
1122+
.amount_msats(1000)
1123+
.build().unwrap()
1124+
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
1125+
.build().unwrap()
1126+
.sign(payer_sign).unwrap()
1127+
.respond_with(payment_paths(), payment_hash(), now()).unwrap()
1128+
.build().unwrap()
1129+
.sign(recipient_sign).unwrap();
1130+
1131+
let mut buffer = Vec::new();
1132+
invoice.write(&mut buffer).unwrap();
1133+
1134+
if let Err(e) = Invoice::try_from(buffer) {
1135+
panic!("error parsing invoice: {:?}", e);
1136+
}
1137+
1138+
let mut tlv_stream = invoice.as_tlv_stream();
1139+
tlv_stream.3.paths = None;
1140+
1141+
match Invoice::try_from(tlv_stream.to_bytes()) {
1142+
Ok(_) => panic!("expected error"),
1143+
Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingPaths)),
1144+
}
1145+
1146+
let mut tlv_stream = invoice.as_tlv_stream();
1147+
tlv_stream.3.blindedpay = None;
1148+
1149+
match Invoice::try_from(tlv_stream.to_bytes()) {
1150+
Ok(_) => panic!("expected error"),
1151+
Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InvalidPayInfo)),
1152+
}
1153+
1154+
let empty_payment_paths = vec![];
1155+
let mut tlv_stream = invoice.as_tlv_stream();
1156+
tlv_stream.3.paths = Some(Iterable(empty_payment_paths.iter().map(|(path, _)| path)));
1157+
1158+
match Invoice::try_from(tlv_stream.to_bytes()) {
1159+
Ok(_) => panic!("expected error"),
1160+
Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingPaths)),
1161+
}
1162+
1163+
let mut payment_paths = payment_paths();
1164+
payment_paths.pop();
1165+
let mut tlv_stream = invoice.as_tlv_stream();
1166+
tlv_stream.3.blindedpay = Some(Iterable(payment_paths.iter().map(|(_, payinfo)| payinfo)));
1167+
1168+
match Invoice::try_from(tlv_stream.to_bytes()) {
1169+
Ok(_) => panic!("expected error"),
1170+
Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InvalidPayInfo)),
1171+
}
1172+
}
1173+
1174+
#[test]
1175+
fn parses_invoice_with_created_at() {
1176+
let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
1177+
.amount_msats(1000)
1178+
.build().unwrap()
1179+
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
1180+
.build().unwrap()
1181+
.sign(payer_sign).unwrap()
1182+
.respond_with(payment_paths(), payment_hash(), now()).unwrap()
1183+
.build().unwrap()
1184+
.sign(recipient_sign).unwrap();
1185+
1186+
let mut buffer = Vec::new();
1187+
invoice.write(&mut buffer).unwrap();
1188+
1189+
if let Err(e) = Invoice::try_from(buffer) {
1190+
panic!("error parsing invoice: {:?}", e);
1191+
}
1192+
1193+
let mut tlv_stream = invoice.as_tlv_stream();
1194+
tlv_stream.3.created_at = None;
1195+
1196+
match Invoice::try_from(tlv_stream.to_bytes()) {
1197+
Ok(_) => panic!("expected error"),
1198+
Err(e) => {
1199+
assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingCreationTime));
1200+
},
1201+
}
1202+
}
1203+
1204+
#[test]
1205+
fn parses_invoice_with_payment_hash() {
1206+
let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
1207+
.amount_msats(1000)
1208+
.build().unwrap()
1209+
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
1210+
.build().unwrap()
1211+
.sign(payer_sign).unwrap()
1212+
.respond_with(payment_paths(), payment_hash(), now()).unwrap()
1213+
.build().unwrap()
1214+
.sign(recipient_sign).unwrap();
1215+
1216+
let mut buffer = Vec::new();
1217+
invoice.write(&mut buffer).unwrap();
1218+
1219+
if let Err(e) = Invoice::try_from(buffer) {
1220+
panic!("error parsing invoice: {:?}", e);
1221+
}
1222+
1223+
let mut tlv_stream = invoice.as_tlv_stream();
1224+
tlv_stream.3.payment_hash = None;
1225+
1226+
match Invoice::try_from(tlv_stream.to_bytes()) {
1227+
Ok(_) => panic!("expected error"),
1228+
Err(e) => {
1229+
assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingPaymentHash));
1230+
},
1231+
}
1232+
}
1233+
1234+
#[test]
1235+
fn parses_invoice_with_amount() {
1236+
let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
1237+
.amount_msats(1000)
1238+
.build().unwrap()
1239+
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
1240+
.build().unwrap()
1241+
.sign(payer_sign).unwrap()
1242+
.respond_with(payment_paths(), payment_hash(), now()).unwrap()
1243+
.build().unwrap()
1244+
.sign(recipient_sign).unwrap();
1245+
1246+
let mut buffer = Vec::new();
1247+
invoice.write(&mut buffer).unwrap();
1248+
1249+
if let Err(e) = Invoice::try_from(buffer) {
1250+
panic!("error parsing invoice: {:?}", e);
1251+
}
1252+
1253+
let mut tlv_stream = invoice.as_tlv_stream();
1254+
tlv_stream.3.amount = None;
1255+
1256+
match Invoice::try_from(tlv_stream.to_bytes()) {
1257+
Ok(_) => panic!("expected error"),
1258+
Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingAmount)),
1259+
}
1260+
}
1261+
1262+
#[test]
1263+
fn parses_invoice_with_node_id() {
1264+
let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
1265+
.amount_msats(1000)
1266+
.build().unwrap()
1267+
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
1268+
.build().unwrap()
1269+
.sign(payer_sign).unwrap()
1270+
.respond_with(payment_paths(), payment_hash(), now()).unwrap()
1271+
.build().unwrap()
1272+
.sign(recipient_sign).unwrap();
1273+
1274+
let mut buffer = Vec::new();
1275+
invoice.write(&mut buffer).unwrap();
1276+
1277+
if let Err(e) = Invoice::try_from(buffer) {
1278+
panic!("error parsing invoice: {:?}", e);
1279+
}
1280+
1281+
let mut tlv_stream = invoice.as_tlv_stream();
1282+
tlv_stream.3.node_id = None;
1283+
1284+
match Invoice::try_from(tlv_stream.to_bytes()) {
1285+
Ok(_) => panic!("expected error"),
1286+
Err(e) => {
1287+
assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingSigningPubkey));
1288+
},
1289+
}
1290+
}
1291+
1292+
#[test]
1293+
fn fails_parsing_invoice_without_signature() {
1294+
let mut buffer = Vec::new();
1295+
OfferBuilder::new("foo".into(), recipient_pubkey())
1296+
.amount_msats(1000)
1297+
.build().unwrap()
1298+
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
1299+
.build().unwrap()
1300+
.sign(payer_sign).unwrap()
1301+
.respond_with(payment_paths(), payment_hash(), now()).unwrap()
1302+
.build().unwrap()
1303+
.invoice
1304+
.write(&mut buffer).unwrap();
1305+
1306+
match Invoice::try_from(buffer) {
1307+
Ok(_) => panic!("expected error"),
1308+
Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingSignature)),
1309+
}
1310+
}
1311+
1312+
#[test]
1313+
fn fails_parsing_invoice_with_invalid_signature() {
1314+
let mut invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
1315+
.amount_msats(1000)
1316+
.build().unwrap()
1317+
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
1318+
.build().unwrap()
1319+
.sign(payer_sign).unwrap()
1320+
.respond_with(payment_paths(), payment_hash(), now()).unwrap()
1321+
.build().unwrap()
1322+
.sign(recipient_sign).unwrap();
1323+
let last_signature_byte = invoice.bytes.last_mut().unwrap();
1324+
*last_signature_byte = last_signature_byte.wrapping_add(1);
1325+
1326+
let mut buffer = Vec::new();
1327+
invoice.write(&mut buffer).unwrap();
1328+
1329+
match Invoice::try_from(buffer) {
1330+
Ok(_) => panic!("expected error"),
1331+
Err(e) => {
1332+
assert_eq!(e, ParseError::InvalidSignature(secp256k1::Error::InvalidSignature));
1333+
},
1334+
}
1335+
}
1336+
1337+
#[test]
1338+
fn fails_parsing_invoice_with_extra_tlv_records() {
1339+
let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
1340+
.amount_msats(1000)
1341+
.build().unwrap()
1342+
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
1343+
.build().unwrap()
1344+
.sign(payer_sign).unwrap()
1345+
.respond_with(payment_paths(), payment_hash(), now()).unwrap()
1346+
.build().unwrap()
1347+
.sign(recipient_sign).unwrap();
1348+
1349+
let mut encoded_invoice = Vec::new();
1350+
invoice.write(&mut encoded_invoice).unwrap();
1351+
BigSize(1002).write(&mut encoded_invoice).unwrap();
1352+
BigSize(32).write(&mut encoded_invoice).unwrap();
1353+
[42u8; 32].write(&mut encoded_invoice).unwrap();
1354+
1355+
match Invoice::try_from(encoded_invoice) {
1356+
Ok(_) => panic!("expected error"),
1357+
Err(e) => assert_eq!(e, ParseError::Decode(DecodeError::InvalidValue)),
1358+
}
1359+
}
10951360
}

lightning/src/util/ser.rs

+18
Original file line numberDiff line numberDiff line change
@@ -1041,6 +1041,24 @@ impl<A: Writeable, B: Writeable, C: Writeable> Writeable for (A, B, C) {
10411041
}
10421042
}
10431043

1044+
impl<A: Readable, B: Readable, C: Readable, D: Readable> Readable for (A, B, C, D) {
1045+
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
1046+
let a: A = Readable::read(r)?;
1047+
let b: B = Readable::read(r)?;
1048+
let c: C = Readable::read(r)?;
1049+
let d: D = Readable::read(r)?;
1050+
Ok((a, b, c, d))
1051+
}
1052+
}
1053+
impl<A: Writeable, B: Writeable, C: Writeable, D: Writeable> Writeable for (A, B, C, D) {
1054+
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
1055+
self.0.write(w)?;
1056+
self.1.write(w)?;
1057+
self.2.write(w)?;
1058+
self.3.write(w)
1059+
}
1060+
}
1061+
10441062
impl Writeable for () {
10451063
fn write<W: Writer>(&self, _: &mut W) -> Result<(), io::Error> {
10461064
Ok(())

0 commit comments

Comments
 (0)