Skip to content

Commit 0c8b3c9

Browse files
committed
Invoice parsing tests
Tests for checking invoice semantics when parsing invoice bytes as defined by BOLT 12.
1 parent 3e1ccdb commit 0c8b3c9

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
@@ -523,6 +523,12 @@ impl Writeable for Invoice {
523523
}
524524
}
525525

526+
impl Writeable for InvoiceContents {
527+
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
528+
self.as_tlv_stream().write(writer)
529+
}
530+
}
531+
526532
impl TryFrom<Vec<u8>> for Invoice {
527533
type Error = ParseError;
528534

@@ -717,7 +723,7 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
717723

718724
#[cfg(test)]
719725
mod tests {
720-
use super::{DEFAULT_RELATIVE_EXPIRY, BlindedPayInfo, FallbackAddress, Invoice, InvoiceTlvStreamRef, SIGNATURE_TAG};
726+
use super::{DEFAULT_RELATIVE_EXPIRY, BlindedPayInfo, FallbackAddress, FullInvoiceTlvStreamRef, Invoice, InvoiceTlvStreamRef, SIGNATURE_TAG};
721727

722728
use bitcoin::blockdata::script::Script;
723729
use bitcoin::hashes::Hash;
@@ -730,15 +736,16 @@ mod tests {
730736
#[cfg(feature = "std")]
731737
use core::time::Duration;
732738
use crate::ln::PaymentHash;
739+
use crate::ln::msgs::DecodeError;
733740
use crate::ln::features::{BlindedHopFeatures, Bolt12InvoiceFeatures};
734741
use crate::offers::invoice_request::InvoiceRequestTlvStreamRef;
735742
use crate::offers::merkle::{SignError, SignatureTlvStreamRef, self};
736743
use crate::offers::offer::{OfferBuilder, OfferTlvStreamRef};
737-
use crate::offers::parse::SemanticError;
744+
use crate::offers::parse::{ParseError, SemanticError};
738745
use crate::offers::payer::PayerTlvStreamRef;
739746
use crate::offers::refund::RefundBuilder;
740747
use crate::onion_message::{BlindedHop, BlindedPath};
741-
use crate::util::ser::{Iterable, Writeable};
748+
use crate::util::ser::{BigSize, Iterable, Writeable};
742749

743750
fn payer_keys() -> KeyPair {
744751
let secp_ctx = Secp256k1::new();
@@ -779,6 +786,22 @@ mod tests {
779786
SecretKey::from_slice(&[byte; 32]).unwrap()
780787
}
781788

789+
trait ToBytes {
790+
fn to_bytes(&self) -> Vec<u8>;
791+
}
792+
793+
impl<'a> ToBytes for FullInvoiceTlvStreamRef<'a> {
794+
fn to_bytes(&self) -> Vec<u8> {
795+
let mut buffer = Vec::new();
796+
self.0.write(&mut buffer).unwrap();
797+
self.1.write(&mut buffer).unwrap();
798+
self.2.write(&mut buffer).unwrap();
799+
self.3.write(&mut buffer).unwrap();
800+
self.4.write(&mut buffer).unwrap();
801+
buffer
802+
}
803+
}
804+
782805
fn payment_paths() -> Vec<(BlindedPath, BlindedPayInfo)> {
783806
let paths = vec![
784807
BlindedPath {
@@ -1165,4 +1188,246 @@ mod tests {
11651188
Err(e) => assert_eq!(e, SignError::Verification(secp256k1::Error::InvalidSignature)),
11661189
}
11671190
}
1191+
1192+
#[test]
1193+
fn parses_invoice_with_payment_paths() {
1194+
let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
1195+
.amount_msats(1000)
1196+
.build().unwrap()
1197+
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
1198+
.build().unwrap()
1199+
.sign(payer_sign).unwrap()
1200+
.respond_with(payment_paths(), payment_hash(), now()).unwrap()
1201+
.build().unwrap()
1202+
.sign(recipient_sign).unwrap();
1203+
1204+
let mut buffer = Vec::new();
1205+
invoice.write(&mut buffer).unwrap();
1206+
1207+
if let Err(e) = Invoice::try_from(buffer) {
1208+
panic!("error parsing invoice: {:?}", e);
1209+
}
1210+
1211+
let mut tlv_stream = invoice.as_tlv_stream();
1212+
tlv_stream.3.paths = None;
1213+
1214+
match Invoice::try_from(tlv_stream.to_bytes()) {
1215+
Ok(_) => panic!("expected error"),
1216+
Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingPaths)),
1217+
}
1218+
1219+
let mut tlv_stream = invoice.as_tlv_stream();
1220+
tlv_stream.3.blindedpay = None;
1221+
1222+
match Invoice::try_from(tlv_stream.to_bytes()) {
1223+
Ok(_) => panic!("expected error"),
1224+
Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InvalidPayInfo)),
1225+
}
1226+
1227+
let empty_payment_paths = vec![];
1228+
let mut tlv_stream = invoice.as_tlv_stream();
1229+
tlv_stream.3.paths = Some(Iterable(empty_payment_paths.iter().map(|(path, _)| path)));
1230+
1231+
match Invoice::try_from(tlv_stream.to_bytes()) {
1232+
Ok(_) => panic!("expected error"),
1233+
Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingPaths)),
1234+
}
1235+
1236+
let mut payment_paths = payment_paths();
1237+
payment_paths.pop();
1238+
let mut tlv_stream = invoice.as_tlv_stream();
1239+
tlv_stream.3.blindedpay = Some(Iterable(payment_paths.iter().map(|(_, payinfo)| payinfo)));
1240+
1241+
match Invoice::try_from(tlv_stream.to_bytes()) {
1242+
Ok(_) => panic!("expected error"),
1243+
Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InvalidPayInfo)),
1244+
}
1245+
}
1246+
1247+
#[test]
1248+
fn parses_invoice_with_created_at() {
1249+
let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
1250+
.amount_msats(1000)
1251+
.build().unwrap()
1252+
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
1253+
.build().unwrap()
1254+
.sign(payer_sign).unwrap()
1255+
.respond_with(payment_paths(), payment_hash(), now()).unwrap()
1256+
.build().unwrap()
1257+
.sign(recipient_sign).unwrap();
1258+
1259+
let mut buffer = Vec::new();
1260+
invoice.write(&mut buffer).unwrap();
1261+
1262+
if let Err(e) = Invoice::try_from(buffer) {
1263+
panic!("error parsing invoice: {:?}", e);
1264+
}
1265+
1266+
let mut tlv_stream = invoice.as_tlv_stream();
1267+
tlv_stream.3.created_at = None;
1268+
1269+
match Invoice::try_from(tlv_stream.to_bytes()) {
1270+
Ok(_) => panic!("expected error"),
1271+
Err(e) => {
1272+
assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingCreationTime));
1273+
},
1274+
}
1275+
}
1276+
1277+
#[test]
1278+
fn parses_invoice_with_payment_hash() {
1279+
let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
1280+
.amount_msats(1000)
1281+
.build().unwrap()
1282+
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
1283+
.build().unwrap()
1284+
.sign(payer_sign).unwrap()
1285+
.respond_with(payment_paths(), payment_hash(), now()).unwrap()
1286+
.build().unwrap()
1287+
.sign(recipient_sign).unwrap();
1288+
1289+
let mut buffer = Vec::new();
1290+
invoice.write(&mut buffer).unwrap();
1291+
1292+
if let Err(e) = Invoice::try_from(buffer) {
1293+
panic!("error parsing invoice: {:?}", e);
1294+
}
1295+
1296+
let mut tlv_stream = invoice.as_tlv_stream();
1297+
tlv_stream.3.payment_hash = None;
1298+
1299+
match Invoice::try_from(tlv_stream.to_bytes()) {
1300+
Ok(_) => panic!("expected error"),
1301+
Err(e) => {
1302+
assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingPaymentHash));
1303+
},
1304+
}
1305+
}
1306+
1307+
#[test]
1308+
fn parses_invoice_with_amount() {
1309+
let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
1310+
.amount_msats(1000)
1311+
.build().unwrap()
1312+
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
1313+
.build().unwrap()
1314+
.sign(payer_sign).unwrap()
1315+
.respond_with(payment_paths(), payment_hash(), now()).unwrap()
1316+
.build().unwrap()
1317+
.sign(recipient_sign).unwrap();
1318+
1319+
let mut buffer = Vec::new();
1320+
invoice.write(&mut buffer).unwrap();
1321+
1322+
if let Err(e) = Invoice::try_from(buffer) {
1323+
panic!("error parsing invoice: {:?}", e);
1324+
}
1325+
1326+
let mut tlv_stream = invoice.as_tlv_stream();
1327+
tlv_stream.3.amount = None;
1328+
1329+
match Invoice::try_from(tlv_stream.to_bytes()) {
1330+
Ok(_) => panic!("expected error"),
1331+
Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingAmount)),
1332+
}
1333+
}
1334+
1335+
#[test]
1336+
fn parses_invoice_with_node_id() {
1337+
let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
1338+
.amount_msats(1000)
1339+
.build().unwrap()
1340+
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
1341+
.build().unwrap()
1342+
.sign(payer_sign).unwrap()
1343+
.respond_with(payment_paths(), payment_hash(), now()).unwrap()
1344+
.build().unwrap()
1345+
.sign(recipient_sign).unwrap();
1346+
1347+
let mut buffer = Vec::new();
1348+
invoice.write(&mut buffer).unwrap();
1349+
1350+
if let Err(e) = Invoice::try_from(buffer) {
1351+
panic!("error parsing invoice: {:?}", e);
1352+
}
1353+
1354+
let mut tlv_stream = invoice.as_tlv_stream();
1355+
tlv_stream.3.node_id = None;
1356+
1357+
match Invoice::try_from(tlv_stream.to_bytes()) {
1358+
Ok(_) => panic!("expected error"),
1359+
Err(e) => {
1360+
assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingSigningPubkey));
1361+
},
1362+
}
1363+
}
1364+
1365+
#[test]
1366+
fn fails_parsing_invoice_without_signature() {
1367+
let mut buffer = Vec::new();
1368+
OfferBuilder::new("foo".into(), recipient_pubkey())
1369+
.amount_msats(1000)
1370+
.build().unwrap()
1371+
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
1372+
.build().unwrap()
1373+
.sign(payer_sign).unwrap()
1374+
.respond_with(payment_paths(), payment_hash(), now()).unwrap()
1375+
.build().unwrap()
1376+
.invoice
1377+
.write(&mut buffer).unwrap();
1378+
1379+
match Invoice::try_from(buffer) {
1380+
Ok(_) => panic!("expected error"),
1381+
Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingSignature)),
1382+
}
1383+
}
1384+
1385+
#[test]
1386+
fn fails_parsing_invoice_with_invalid_signature() {
1387+
let mut invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
1388+
.amount_msats(1000)
1389+
.build().unwrap()
1390+
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
1391+
.build().unwrap()
1392+
.sign(payer_sign).unwrap()
1393+
.respond_with(payment_paths(), payment_hash(), now()).unwrap()
1394+
.build().unwrap()
1395+
.sign(recipient_sign).unwrap();
1396+
let last_signature_byte = invoice.bytes.last_mut().unwrap();
1397+
*last_signature_byte = last_signature_byte.wrapping_add(1);
1398+
1399+
let mut buffer = Vec::new();
1400+
invoice.write(&mut buffer).unwrap();
1401+
1402+
match Invoice::try_from(buffer) {
1403+
Ok(_) => panic!("expected error"),
1404+
Err(e) => {
1405+
assert_eq!(e, ParseError::InvalidSignature(secp256k1::Error::InvalidSignature));
1406+
},
1407+
}
1408+
}
1409+
1410+
#[test]
1411+
fn fails_parsing_invoice_with_extra_tlv_records() {
1412+
let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
1413+
.amount_msats(1000)
1414+
.build().unwrap()
1415+
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
1416+
.build().unwrap()
1417+
.sign(payer_sign).unwrap()
1418+
.respond_with(payment_paths(), payment_hash(), now()).unwrap()
1419+
.build().unwrap()
1420+
.sign(recipient_sign).unwrap();
1421+
1422+
let mut encoded_invoice = Vec::new();
1423+
invoice.write(&mut encoded_invoice).unwrap();
1424+
BigSize(1002).write(&mut encoded_invoice).unwrap();
1425+
BigSize(32).write(&mut encoded_invoice).unwrap();
1426+
[42u8; 32].write(&mut encoded_invoice).unwrap();
1427+
1428+
match Invoice::try_from(encoded_invoice) {
1429+
Ok(_) => panic!("expected error"),
1430+
Err(e) => assert_eq!(e, ParseError::Decode(DecodeError::InvalidValue)),
1431+
}
1432+
}
11681433
}

lightning/src/util/ser.rs

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

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

0 commit comments

Comments
 (0)