Skip to content

Commit

Permalink
Invoice parsing tests
Browse files Browse the repository at this point in the history
Tests for checking invoice semantics when parsing invoice bytes as
defined by BOLT 12.
  • Loading branch information
jkczyz committed Jan 16, 2023
1 parent b5b4a75 commit c97748a
Show file tree
Hide file tree
Showing 2 changed files with 286 additions and 3 deletions.
271 changes: 268 additions & 3 deletions lightning/src/offers/invoice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,12 @@ impl Writeable for Invoice {
}
}

impl Writeable for InvoiceContents {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
self.as_tlv_stream().write(writer)
}
}

impl TryFrom<Vec<u8>> for Invoice {
type Error = ParseError;

Expand Down Expand Up @@ -644,7 +650,7 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {

#[cfg(test)]
mod tests {
use super::{DEFAULT_RELATIVE_EXPIRY, BlindedPayInfo, FallbackAddress, Invoice, InvoiceTlvStreamRef, SIGNATURE_TAG};
use super::{DEFAULT_RELATIVE_EXPIRY, BlindedPayInfo, FallbackAddress, FullInvoiceTlvStreamRef, Invoice, InvoiceTlvStreamRef, SIGNATURE_TAG};

use bitcoin::blockdata::script::Script;
use bitcoin::hashes::Hash;
Expand All @@ -657,15 +663,16 @@ mod tests {
#[cfg(feature = "std")]
use core::time::Duration;
use crate::ln::PaymentHash;
use crate::ln::msgs::DecodeError;
use crate::ln::features::{BlindedHopFeatures, Bolt12InvoiceFeatures};
use crate::offers::invoice_request::InvoiceRequestTlvStreamRef;
use crate::offers::merkle::{SignError, SignatureTlvStreamRef, self};
use crate::offers::offer::{OfferBuilder, OfferTlvStreamRef};
use crate::offers::parse::SemanticError;
use crate::offers::parse::{ParseError, SemanticError};
use crate::offers::payer::PayerTlvStreamRef;
use crate::offers::refund::RefundBuilder;
use crate::onion_message::{BlindedHop, BlindedPath};
use crate::util::ser::{Iterable, Writeable};
use crate::util::ser::{BigSize, Iterable, Writeable};

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

trait ToBytes {
fn to_bytes(&self) -> Vec<u8>;
}

impl<'a> ToBytes for FullInvoiceTlvStreamRef<'a> {
fn to_bytes(&self) -> Vec<u8> {
let mut buffer = Vec::new();
self.0.write(&mut buffer).unwrap();
self.1.write(&mut buffer).unwrap();
self.2.write(&mut buffer).unwrap();
self.3.write(&mut buffer).unwrap();
self.4.write(&mut buffer).unwrap();
buffer
}
}

fn payment_paths() -> Vec<(BlindedPath, BlindedPayInfo)> {
let paths = vec![
BlindedPath {
Expand Down Expand Up @@ -1092,4 +1115,246 @@ mod tests {
Err(e) => assert_eq!(e, SignError::Verification(secp256k1::Error::InvalidSignature)),
}
}

#[test]
fn parses_invoice_with_payment_paths() {
let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
.amount_msats(1000)
.build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap()
.sign(payer_sign).unwrap()
.respond_with(payment_paths(), payment_hash(), now()).unwrap()
.build().unwrap()
.sign(recipient_sign).unwrap();

let mut buffer = Vec::new();
invoice.write(&mut buffer).unwrap();

if let Err(e) = Invoice::try_from(buffer) {
panic!("error parsing invoice: {:?}", e);
}

let mut tlv_stream = invoice.as_tlv_stream();
tlv_stream.3.paths = None;

match Invoice::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingPaths)),
}

let mut tlv_stream = invoice.as_tlv_stream();
tlv_stream.3.blindedpay = None;

match Invoice::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InvalidPayInfo)),
}

let empty_payment_paths = vec![];
let mut tlv_stream = invoice.as_tlv_stream();
tlv_stream.3.paths = Some(Iterable(empty_payment_paths.iter().map(|(path, _)| path)));

match Invoice::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingPaths)),
}

let mut payment_paths = payment_paths();
payment_paths.pop();
let mut tlv_stream = invoice.as_tlv_stream();
tlv_stream.3.blindedpay = Some(Iterable(payment_paths.iter().map(|(_, payinfo)| payinfo)));

match Invoice::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InvalidPayInfo)),
}
}

#[test]
fn parses_invoice_with_created_at() {
let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
.amount_msats(1000)
.build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap()
.sign(payer_sign).unwrap()
.respond_with(payment_paths(), payment_hash(), now()).unwrap()
.build().unwrap()
.sign(recipient_sign).unwrap();

let mut buffer = Vec::new();
invoice.write(&mut buffer).unwrap();

if let Err(e) = Invoice::try_from(buffer) {
panic!("error parsing invoice: {:?}", e);
}

let mut tlv_stream = invoice.as_tlv_stream();
tlv_stream.3.created_at = None;

match Invoice::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
Err(e) => {
assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingCreationTime));
},
}
}

#[test]
fn parses_invoice_with_payment_hash() {
let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
.amount_msats(1000)
.build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap()
.sign(payer_sign).unwrap()
.respond_with(payment_paths(), payment_hash(), now()).unwrap()
.build().unwrap()
.sign(recipient_sign).unwrap();

let mut buffer = Vec::new();
invoice.write(&mut buffer).unwrap();

if let Err(e) = Invoice::try_from(buffer) {
panic!("error parsing invoice: {:?}", e);
}

let mut tlv_stream = invoice.as_tlv_stream();
tlv_stream.3.payment_hash = None;

match Invoice::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
Err(e) => {
assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingPaymentHash));
},
}
}

#[test]
fn parses_invoice_with_amount() {
let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
.amount_msats(1000)
.build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap()
.sign(payer_sign).unwrap()
.respond_with(payment_paths(), payment_hash(), now()).unwrap()
.build().unwrap()
.sign(recipient_sign).unwrap();

let mut buffer = Vec::new();
invoice.write(&mut buffer).unwrap();

if let Err(e) = Invoice::try_from(buffer) {
panic!("error parsing invoice: {:?}", e);
}

let mut tlv_stream = invoice.as_tlv_stream();
tlv_stream.3.amount = None;

match Invoice::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingAmount)),
}
}

#[test]
fn parses_invoice_with_node_id() {
let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
.amount_msats(1000)
.build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap()
.sign(payer_sign).unwrap()
.respond_with(payment_paths(), payment_hash(), now()).unwrap()
.build().unwrap()
.sign(recipient_sign).unwrap();

let mut buffer = Vec::new();
invoice.write(&mut buffer).unwrap();

if let Err(e) = Invoice::try_from(buffer) {
panic!("error parsing invoice: {:?}", e);
}

let mut tlv_stream = invoice.as_tlv_stream();
tlv_stream.3.node_id = None;

match Invoice::try_from(tlv_stream.to_bytes()) {
Ok(_) => panic!("expected error"),
Err(e) => {
assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingSigningPubkey));
},
}
}

#[test]
fn fails_parsing_invoice_without_signature() {
let mut buffer = Vec::new();
OfferBuilder::new("foo".into(), recipient_pubkey())
.amount_msats(1000)
.build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap()
.sign(payer_sign).unwrap()
.respond_with(payment_paths(), payment_hash(), now()).unwrap()
.build().unwrap()
.invoice
.write(&mut buffer).unwrap();

match Invoice::try_from(buffer) {
Ok(_) => panic!("expected error"),
Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingSignature)),
}
}

#[test]
fn fails_parsing_invoice_with_invalid_signature() {
let mut invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
.amount_msats(1000)
.build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap()
.sign(payer_sign).unwrap()
.respond_with(payment_paths(), payment_hash(), now()).unwrap()
.build().unwrap()
.sign(recipient_sign).unwrap();
let last_signature_byte = invoice.bytes.last_mut().unwrap();
*last_signature_byte = last_signature_byte.wrapping_add(1);

let mut buffer = Vec::new();
invoice.write(&mut buffer).unwrap();

match Invoice::try_from(buffer) {
Ok(_) => panic!("expected error"),
Err(e) => {
assert_eq!(e, ParseError::InvalidSignature(secp256k1::Error::InvalidSignature));
},
}
}

#[test]
fn fails_parsing_invoice_with_extra_tlv_records() {
let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
.amount_msats(1000)
.build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap()
.sign(payer_sign).unwrap()
.respond_with(payment_paths(), payment_hash(), now()).unwrap()
.build().unwrap()
.sign(recipient_sign).unwrap();

let mut encoded_invoice = Vec::new();
invoice.write(&mut encoded_invoice).unwrap();
BigSize(1002).write(&mut encoded_invoice).unwrap();
BigSize(32).write(&mut encoded_invoice).unwrap();
[42u8; 32].write(&mut encoded_invoice).unwrap();

match Invoice::try_from(encoded_invoice) {
Ok(_) => panic!("expected error"),
Err(e) => assert_eq!(e, ParseError::Decode(DecodeError::InvalidValue)),
}
}
}
18 changes: 18 additions & 0 deletions lightning/src/util/ser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1041,6 +1041,24 @@ impl<A: Writeable, B: Writeable, C: Writeable> Writeable for (A, B, C) {
}
}

impl<A: Readable, B: Readable, C: Readable, D: Readable> Readable for (A, B, C, D) {
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
let a: A = Readable::read(r)?;
let b: B = Readable::read(r)?;
let c: C = Readable::read(r)?;
let d: D = Readable::read(r)?;
Ok((a, b, c, d))
}
}
impl<A: Writeable, B: Writeable, C: Writeable, D: Writeable> Writeable for (A, B, C, D) {
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
self.0.write(w)?;
self.1.write(w)?;
self.2.write(w)?;
self.3.write(w)
}
}

impl Writeable for () {
fn write<W: Writer>(&self, _: &mut W) -> Result<(), io::Error> {
Ok(())
Expand Down

0 comments on commit c97748a

Please sign in to comment.