Skip to content

Commit 63585b5

Browse files
committed
Invoice request building tests
Tests for checking invoice_request message semantics when building as defined by BOLT 12.
1 parent 40cf6a5 commit 63585b5

File tree

1 file changed

+362
-5
lines changed

1 file changed

+362
-5
lines changed

lightning/src/offers/invoice_request.rs

Lines changed: 362 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,16 @@ impl InvoiceRequest {
303303
pub fn signature(&self) -> Option<Signature> {
304304
self.signature
305305
}
306+
307+
#[cfg(test)]
308+
fn as_tlv_stream(&self) -> FullInvoiceRequestTlvStreamRef {
309+
let (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream) =
310+
self.contents.as_tlv_stream();
311+
let signature_tlv_stream = SignatureTlvStreamRef {
312+
signature: self.signature.as_ref(),
313+
};
314+
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, signature_tlv_stream)
315+
}
306316
}
307317

308318
impl InvoiceRequestContents {
@@ -359,6 +369,14 @@ tlv_stream!(InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef, 80..160, {
359369
type FullInvoiceRequestTlvStream =
360370
(PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, SignatureTlvStream);
361371

372+
#[cfg(test)]
373+
type FullInvoiceRequestTlvStreamRef<'a> = (
374+
PayerTlvStreamRef<'a>,
375+
OfferTlvStreamRef<'a>,
376+
InvoiceRequestTlvStreamRef<'a>,
377+
SignatureTlvStreamRef<'a>,
378+
);
379+
362380
impl SeekReadable for FullInvoiceRequestTlvStream {
363381
fn read<R: io::Read + io::Seek>(r: &mut R) -> Result<Self, DecodeError> {
364382
let payer = SeekReadable::read(r)?;
@@ -458,21 +476,360 @@ impl TryFrom<PartialInvoiceRequestTlvStream> for InvoiceRequestContents {
458476
mod tests {
459477
use super::InvoiceRequest;
460478

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

468826
#[test]
469827
fn fails_parsing_invoice_request_with_extra_tlv_records() {
470828
let secp_ctx = Secp256k1::new();
471829
let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
472830
let invoice_request = OfferBuilder::new("foo".into(), keys.public_key())
473831
.amount_msats(1000)
474-
.build()
475-
.unwrap()
832+
.build().unwrap()
476833
.request_invoice(keys.public_key())
477834
.build().unwrap()
478835
.sign(|digest| secp_ctx.sign_schnorr_no_aux_rand(digest, &keys)).unwrap();

0 commit comments

Comments
 (0)