Skip to content

Commit 856030c

Browse files
committed
Invoice request building tests
Tests for checking invoice_request message semantics when building as defined by BOLT 12.
1 parent 3c37984 commit 856030c

File tree

1 file changed

+361
-3
lines changed

1 file changed

+361
-3
lines changed

lightning/src/offers/invoice_request.rs

Lines changed: 361 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,16 @@ impl InvoiceRequest {
293293
pub fn signature(&self) -> Option<Signature> {
294294
self.signature
295295
}
296+
297+
#[cfg(test)]
298+
fn as_tlv_stream(&self) -> FullInvoiceRequestTlvStreamRef {
299+
let (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream) =
300+
self.contents.as_tlv_stream();
301+
let signature_tlv_stream = SignatureTlvStreamRef {
302+
signature: self.signature.as_ref(),
303+
};
304+
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, signature_tlv_stream)
305+
}
296306
}
297307

298308
impl InvoiceRequestContents {
@@ -349,6 +359,14 @@ tlv_stream!(InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef, 80..160, {
349359
type FullInvoiceRequestTlvStream =
350360
(PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, SignatureTlvStream);
351361

362+
#[cfg(test)]
363+
type FullInvoiceRequestTlvStreamRef<'a> = (
364+
PayerTlvStreamRef<'a>,
365+
OfferTlvStreamRef<'a>,
366+
InvoiceRequestTlvStreamRef<'a>,
367+
SignatureTlvStreamRef<'a>,
368+
);
369+
352370
impl SeekReadable for FullInvoiceRequestTlvStream {
353371
fn read<R: io::Read + io::Seek>(r: &mut R) -> Result<Self, DecodeError> {
354372
let payer = SeekReadable::read(r)?;
@@ -443,12 +461,352 @@ impl TryFrom<PartialInvoiceRequestTlvStream> for InvoiceRequestContents {
443461
mod tests {
444462
use super::InvoiceRequest;
445463

446-
use bitcoin::secp256k1::{KeyPair, Secp256k1, SecretKey};
464+
use bitcoin::blockdata::constants::ChainHash;
465+
use bitcoin::network::constants::Network;
466+
use bitcoin::secp256k1::{KeyPair, Message, PublicKey, Secp256k1, SecretKey};
467+
use bitcoin::secp256k1::schnorr::Signature;
447468
use core::convert::TryFrom;
469+
use core::num::NonZeroU64;
470+
use crate::ln::features::InvoiceRequestFeatures;
448471
use crate::ln::msgs::DecodeError;
449-
use crate::offers::offer::OfferBuilder;
450-
use crate::offers::parse::ParseError;
472+
use crate::offers::offer::{OfferBuilder, Quantity};
473+
use crate::offers::parse::{ParseError, SemanticError};
451474
use crate::util::ser::{BigSize, Writeable};
475+
use crate::util::string::PrintableString;
476+
477+
fn payer_keys() -> KeyPair {
478+
let secp_ctx = Secp256k1::new();
479+
KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap())
480+
}
481+
482+
fn payer_sign(digest: &Message) -> Signature {
483+
let secp_ctx = Secp256k1::new();
484+
let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
485+
secp_ctx.sign_schnorr_no_aux_rand(digest, &keys)
486+
}
487+
488+
fn payer_pubkey() -> PublicKey {
489+
payer_keys().public_key()
490+
}
491+
492+
fn recipient_pubkey() -> PublicKey {
493+
let secp_ctx = Secp256k1::new();
494+
KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[43; 32]).unwrap()).public_key()
495+
}
496+
497+
#[test]
498+
fn builds_invoice_request_with_defaults() {
499+
let offer = OfferBuilder::new("foo".into(), recipient_pubkey())
500+
.amount_msats(1000)
501+
.build().unwrap();
502+
let invoice_request = offer.request_invoice(payer_pubkey())
503+
.build().unwrap().sign(payer_sign).unwrap();
504+
505+
let (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, signature_tlv_stream) =
506+
invoice_request.as_tlv_stream();
507+
let mut buffer = Vec::new();
508+
invoice_request.write(&mut buffer).unwrap();
509+
510+
assert_eq!(invoice_request.bytes, buffer.as_slice());
511+
assert_eq!(invoice_request.metadata(), None);
512+
assert_eq!(invoice_request.chain(), ChainHash::using_genesis_block(Network::Bitcoin));
513+
assert_eq!(invoice_request.amount_msats(), None);
514+
assert_eq!(invoice_request.features(), &InvoiceRequestFeatures::empty());
515+
assert_eq!(invoice_request.quantity(), None);
516+
assert_eq!(invoice_request.payer_id(), payer_pubkey());
517+
assert_eq!(invoice_request.payer_note(), None);
518+
assert!(invoice_request.signature().is_some());
519+
520+
assert_eq!(payer_tlv_stream.metadata, None);
521+
assert_eq!(offer_tlv_stream.chains, None);
522+
assert_eq!(offer_tlv_stream.metadata, None);
523+
assert_eq!(offer_tlv_stream.currency, None);
524+
assert_eq!(offer_tlv_stream.amount, Some(1000));
525+
assert_eq!(offer_tlv_stream.description, Some(&String::from("foo")));
526+
assert_eq!(offer_tlv_stream.features, None);
527+
assert_eq!(offer_tlv_stream.absolute_expiry, None);
528+
assert_eq!(offer_tlv_stream.paths, None);
529+
assert_eq!(offer_tlv_stream.issuer, None);
530+
assert_eq!(offer_tlv_stream.quantity_max, None);
531+
assert_eq!(offer_tlv_stream.node_id, Some(&recipient_pubkey()));
532+
assert_eq!(invoice_request_tlv_stream.chain, None);
533+
assert_eq!(invoice_request_tlv_stream.amount, None);
534+
assert_eq!(invoice_request_tlv_stream.features, None);
535+
assert_eq!(invoice_request_tlv_stream.quantity, None);
536+
assert_eq!(invoice_request_tlv_stream.payer_id, Some(&payer_pubkey()));
537+
assert_eq!(invoice_request_tlv_stream.payer_note, None);
538+
assert!(signature_tlv_stream.signature.is_some());
539+
540+
if let Err(e) = InvoiceRequest::try_from(buffer) {
541+
panic!("error parsing offer: {:?}", e);
542+
}
543+
}
544+
545+
#[test]
546+
fn builds_invoice_request_with_metadata() {
547+
let offer = OfferBuilder::new("foo".into(), recipient_pubkey())
548+
.amount_msats(1000)
549+
.build().unwrap();
550+
551+
let invoice_request = offer.request_invoice(payer_pubkey())
552+
.metadata(vec![42; 32])
553+
.build().unwrap()
554+
.sign(payer_sign).unwrap();
555+
let (tlv_stream, _, _, _) = invoice_request.as_tlv_stream();
556+
assert_eq!(invoice_request.metadata(), Some(&vec![42; 32]));
557+
assert_eq!(tlv_stream.metadata, Some(&vec![42; 32]));
558+
559+
let invoice_request = offer.request_invoice(payer_pubkey())
560+
.metadata(vec![42; 32])
561+
.metadata(vec![43; 32])
562+
.build().unwrap()
563+
.sign(payer_sign).unwrap();
564+
let (tlv_stream, _, _, _) = invoice_request.as_tlv_stream();
565+
assert_eq!(invoice_request.metadata(), Some(&vec![43; 32]));
566+
assert_eq!(tlv_stream.metadata, Some(&vec![43; 32]));
567+
}
568+
569+
#[test]
570+
fn builds_invoice_request_with_chain() {
571+
let mainnet = ChainHash::using_genesis_block(Network::Bitcoin);
572+
let testnet = ChainHash::using_genesis_block(Network::Testnet);
573+
574+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
575+
.amount_msats(1000)
576+
.build().unwrap()
577+
.request_invoice(payer_pubkey())
578+
.chain(Network::Bitcoin)
579+
.build().unwrap()
580+
.sign(payer_sign).unwrap();
581+
let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream();
582+
assert_eq!(invoice_request.chain(), mainnet);
583+
assert_eq!(tlv_stream.chain, None);
584+
585+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
586+
.amount_msats(1000)
587+
.chain(Network::Testnet)
588+
.build().unwrap()
589+
.request_invoice(payer_pubkey())
590+
.chain(Network::Testnet)
591+
.build().unwrap()
592+
.sign(payer_sign).unwrap();
593+
let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream();
594+
assert_eq!(invoice_request.chain(), testnet);
595+
assert_eq!(tlv_stream.chain, Some(&testnet));
596+
597+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
598+
.amount_msats(1000)
599+
.chain(Network::Bitcoin)
600+
.chain(Network::Testnet)
601+
.build().unwrap()
602+
.request_invoice(payer_pubkey())
603+
.chain(Network::Bitcoin)
604+
.build().unwrap()
605+
.sign(payer_sign).unwrap();
606+
let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream();
607+
assert_eq!(invoice_request.chain(), mainnet);
608+
assert_eq!(tlv_stream.chain, None);
609+
610+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
611+
.amount_msats(1000)
612+
.chain(Network::Testnet)
613+
.build().unwrap()
614+
.request_invoice(payer_pubkey())
615+
.chain(Network::Bitcoin)
616+
.chain(Network::Testnet)
617+
.build().unwrap()
618+
.sign(payer_sign).unwrap();
619+
let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream();
620+
assert_eq!(invoice_request.chain(), testnet);
621+
assert_eq!(tlv_stream.chain, Some(&testnet));
622+
623+
match OfferBuilder::new("foo".into(), recipient_pubkey())
624+
.amount_msats(1000)
625+
.chain(Network::Testnet)
626+
.build().unwrap()
627+
.request_invoice(payer_pubkey())
628+
.chain(Network::Bitcoin)
629+
.build()
630+
{
631+
Ok(_) => panic!("expected error"),
632+
Err(e) => assert_eq!(e, SemanticError::UnsupportedChain),
633+
}
634+
}
635+
636+
#[test]
637+
fn builds_invoice_request_with_amount() {
638+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
639+
.amount_msats(1000)
640+
.build().unwrap()
641+
.request_invoice(payer_pubkey())
642+
.amount_msats(1000)
643+
.build().unwrap()
644+
.sign(payer_sign).unwrap();
645+
let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream();
646+
assert_eq!(invoice_request.amount_msats(), Some(1000));
647+
assert_eq!(tlv_stream.amount, Some(1000));
648+
649+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
650+
.amount_msats(1000)
651+
.build().unwrap()
652+
.request_invoice(payer_pubkey())
653+
.amount_msats(999)
654+
.amount_msats(1000)
655+
.build().unwrap()
656+
.sign(payer_sign).unwrap();
657+
let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream();
658+
assert_eq!(invoice_request.amount_msats(), Some(1000));
659+
assert_eq!(tlv_stream.amount, Some(1000));
660+
661+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
662+
.amount_msats(1000)
663+
.build().unwrap()
664+
.request_invoice(payer_pubkey())
665+
.amount_msats(1001)
666+
.build().unwrap()
667+
.sign(payer_sign).unwrap();
668+
let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream();
669+
assert_eq!(invoice_request.amount_msats(), Some(1001));
670+
assert_eq!(tlv_stream.amount, Some(1001));
671+
672+
match OfferBuilder::new("foo".into(), recipient_pubkey())
673+
.amount_msats(1000)
674+
.build().unwrap()
675+
.request_invoice(payer_pubkey())
676+
.amount_msats(999)
677+
.build()
678+
{
679+
Ok(_) => panic!("expected error"),
680+
Err(e) => assert_eq!(e, SemanticError::InsufficientAmount),
681+
}
682+
683+
match OfferBuilder::new("foo".into(), recipient_pubkey())
684+
.amount_msats(1000)
685+
.supported_quantity(Quantity::Unbounded)
686+
.build().unwrap()
687+
.request_invoice(payer_pubkey())
688+
.amount_msats(1000)
689+
.quantity(2)
690+
.build()
691+
{
692+
Ok(_) => panic!("expected error"),
693+
Err(e) => assert_eq!(e, SemanticError::InsufficientAmount),
694+
}
695+
696+
match OfferBuilder::new("foo".into(), recipient_pubkey())
697+
.build().unwrap()
698+
.request_invoice(payer_pubkey())
699+
.build()
700+
{
701+
Ok(_) => panic!("expected error"),
702+
Err(e) => assert_eq!(e, SemanticError::MissingAmount),
703+
}
704+
}
705+
706+
#[test]
707+
fn builds_invoice_request_with_quantity() {
708+
let ten = NonZeroU64::new(10).unwrap();
709+
710+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
711+
.amount_msats(1000)
712+
.supported_quantity(Quantity::one())
713+
.build().unwrap()
714+
.request_invoice(payer_pubkey())
715+
.build().unwrap()
716+
.sign(payer_sign).unwrap();
717+
let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream();
718+
assert_eq!(invoice_request.quantity(), None);
719+
assert_eq!(tlv_stream.quantity, None);
720+
721+
match OfferBuilder::new("foo".into(), recipient_pubkey())
722+
.amount_msats(1000)
723+
.supported_quantity(Quantity::one())
724+
.build().unwrap()
725+
.request_invoice(payer_pubkey())
726+
.amount_msats(2_000)
727+
.quantity(2)
728+
.build()
729+
{
730+
Ok(_) => panic!("expected error"),
731+
Err(e) => assert_eq!(e, SemanticError::UnexpectedQuantity),
732+
}
733+
734+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
735+
.amount_msats(1000)
736+
.supported_quantity(Quantity::Bounded(ten))
737+
.build().unwrap()
738+
.request_invoice(payer_pubkey())
739+
.amount_msats(10_000)
740+
.quantity(10)
741+
.build().unwrap()
742+
.sign(payer_sign).unwrap();
743+
let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream();
744+
assert_eq!(invoice_request.amount_msats(), Some(10_000));
745+
assert_eq!(tlv_stream.amount, Some(10_000));
746+
747+
match OfferBuilder::new("foo".into(), recipient_pubkey())
748+
.amount_msats(1000)
749+
.supported_quantity(Quantity::Bounded(ten))
750+
.build().unwrap()
751+
.request_invoice(payer_pubkey())
752+
.amount_msats(11_000)
753+
.quantity(11)
754+
.build()
755+
{
756+
Ok(_) => panic!("expected error"),
757+
Err(e) => assert_eq!(e, SemanticError::InvalidQuantity),
758+
}
759+
760+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
761+
.amount_msats(1000)
762+
.supported_quantity(Quantity::Unbounded)
763+
.build().unwrap()
764+
.request_invoice(payer_pubkey())
765+
.amount_msats(2_000)
766+
.quantity(2)
767+
.build().unwrap()
768+
.sign(payer_sign).unwrap();
769+
let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream();
770+
assert_eq!(invoice_request.amount_msats(), Some(2_000));
771+
assert_eq!(tlv_stream.amount, Some(2_000));
772+
773+
match OfferBuilder::new("foo".into(), recipient_pubkey())
774+
.amount_msats(1000)
775+
.supported_quantity(Quantity::Unbounded)
776+
.build().unwrap()
777+
.request_invoice(payer_pubkey())
778+
.build()
779+
{
780+
Ok(_) => panic!("expected error"),
781+
Err(e) => assert_eq!(e, SemanticError::MissingQuantity),
782+
}
783+
}
784+
785+
#[test]
786+
fn builds_invoice_request_with_payer_note() {
787+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
788+
.amount_msats(1000)
789+
.build().unwrap()
790+
.request_invoice(payer_pubkey())
791+
.payer_note("bar".into())
792+
.build().unwrap()
793+
.sign(payer_sign).unwrap();
794+
let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream();
795+
assert_eq!(invoice_request.payer_note(), Some(PrintableString("bar")));
796+
assert_eq!(tlv_stream.payer_note, Some(&String::from("bar")));
797+
798+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
799+
.amount_msats(1000)
800+
.build().unwrap()
801+
.request_invoice(payer_pubkey())
802+
.payer_note("bar".into())
803+
.payer_note("baz".into())
804+
.build().unwrap()
805+
.sign(payer_sign).unwrap();
806+
let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream();
807+
assert_eq!(invoice_request.payer_note(), Some(PrintableString("baz")));
808+
assert_eq!(tlv_stream.payer_note, Some(&String::from("baz")));
809+
}
452810

453811
#[test]
454812
fn fails_parsing_invoice_request_with_extra_tlv_records() {

0 commit comments

Comments
 (0)