Skip to content

Commit 84059d3

Browse files
committed
Invoice request building tests
Tests for checking invoice_request message semantics when building as defined by BOLT 12.
1 parent da41691 commit 84059d3

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
@@ -308,6 +308,16 @@ impl InvoiceRequest {
308308
pub fn signature(&self) -> Option<Signature> {
309309
self.signature
310310
}
311+
312+
#[cfg(test)]
313+
fn as_tlv_stream(&self) -> FullInvoiceRequestTlvStreamRef {
314+
let (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream) =
315+
self.contents.as_tlv_stream();
316+
let signature_tlv_stream = SignatureTlvStreamRef {
317+
signature: self.signature.as_ref(),
318+
};
319+
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, signature_tlv_stream)
320+
}
311321
}
312322

313323
impl InvoiceRequestContents {
@@ -364,6 +374,14 @@ tlv_stream!(InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef, 80..160, {
364374
type FullInvoiceRequestTlvStream =
365375
(PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, SignatureTlvStream);
366376

377+
#[cfg(test)]
378+
type FullInvoiceRequestTlvStreamRef<'a> = (
379+
PayerTlvStreamRef<'a>,
380+
OfferTlvStreamRef<'a>,
381+
InvoiceRequestTlvStreamRef<'a>,
382+
SignatureTlvStreamRef<'a>,
383+
);
384+
367385
impl SeekReadable for FullInvoiceRequestTlvStream {
368386
fn read<R: io::Read + io::Seek>(r: &mut R) -> Result<Self, DecodeError> {
369387
let payer = SeekReadable::read(r)?;
@@ -463,12 +481,352 @@ impl TryFrom<PartialInvoiceRequestTlvStream> for InvoiceRequestContents {
463481
mod tests {
464482
use super::InvoiceRequest;
465483

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

473831
#[test]
474832
fn fails_parsing_invoice_request_with_extra_tlv_records() {

0 commit comments

Comments
 (0)