X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Foffers%2Finvoice_request.rs;h=fd5ecda558af523f1664452f98968edef311da9c;hb=5824e226cad67e32d5e8be71ebbb6f91a3fc2116;hp=90f6c183c0ee23ba1b1b6cf5a0e3b45b10d5f6af;hpb=a4df59b37711215ece62ee8da3d2c5d9b537de4e;p=rust-lightning diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index 90f6c183..fd5ecda5 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -9,13 +9,14 @@ //! Data structures and encoding for `invoice_request` messages. //! -//! An [`InvoiceRequest`] can be either built from a parsed [`Offer`] as an "offer to be paid" or -//! built directly as an "offer for money" (e.g., refund, ATM withdrawal). In the former case, it is +//! An [`InvoiceRequest`] can be built from a parsed [`Offer`] as an "offer to be paid". It is //! typically constructed by a customer and sent to the merchant who had published the corresponding -//! offer. In the latter case, an offer doesn't exist as a precursor to the request. Rather the -//! merchant would typically construct the invoice request and present it to the customer. +//! offer. The recipient of the request responds with an `Invoice`. //! -//! The recipient of the request responds with an `Invoice`. +//! For an "offer for money" (e.g., refund, ATM withdrawal), where an offer doesn't exist as a +//! precursor, see [`Refund`]. +//! +//! [`Refund`]: crate::offers::refund::Refund //! //! ```ignore //! extern crate bitcoin; @@ -34,7 +35,6 @@ //! let pubkey = PublicKey::from(keys); //! let mut buffer = Vec::new(); //! -//! // "offer to be paid" flow //! "lno1qcp4256ypq" //! .parse::()? //! .request_invoice(vec![42; 64], pubkey)? @@ -223,11 +223,11 @@ impl<'a> UnsignedInvoiceRequest<'a> { unsigned_tlv_stream.write(&mut bytes).unwrap(); let pubkey = self.invoice_request.payer_id; - let signature = Some(merkle::sign_message(sign, SIGNATURE_TAG, &bytes, pubkey)?); + let signature = merkle::sign_message(sign, SIGNATURE_TAG, &bytes, pubkey)?; // Append the signature TLV record to the bytes. let signature_tlv_stream = SignatureTlvStreamRef { - signature: signature.as_ref(), + signature: Some(&signature), }; signature_tlv_stream.write(&mut bytes).unwrap(); @@ -249,7 +249,7 @@ impl<'a> UnsignedInvoiceRequest<'a> { pub struct InvoiceRequest { pub(super) bytes: Vec, contents: InvoiceRequestContents, - signature: Option, + signature: Signature, } /// The contents of an [`InvoiceRequest`], which may be shared with an `Invoice`. @@ -287,7 +287,7 @@ impl InvoiceRequest { self.contents.amount_msats } - /// Features for paying the invoice. + /// Features pertaining to requesting an invoice. pub fn features(&self) -> &InvoiceRequestFeatures { &self.contents.features } @@ -311,7 +311,7 @@ impl InvoiceRequest { /// Signature of the invoice request using [`payer_id`]. /// /// [`payer_id`]: Self::payer_id - pub fn signature(&self) -> Option { + pub fn signature(&self) -> Signature { self.signature } @@ -320,7 +320,7 @@ impl InvoiceRequest { let (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream) = self.contents.as_tlv_stream(); let signature_tlv_stream = SignatureTlvStreamRef { - signature: self.signature.as_ref(), + signature: Some(&self.signature), }; (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, signature_tlv_stream) } @@ -371,7 +371,7 @@ impl Writeable for InvoiceRequestContents { tlv_stream!(InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef, 80..160, { (80, chain: ChainHash), (82, amount: (u64, HighZeroBytesDroppedBigSize)), - (84, features: InvoiceRequestFeatures), + (84, features: (InvoiceRequestFeatures, WithoutLength)), (86, quantity: (u64, HighZeroBytesDroppedBigSize)), (88, payer_id: PublicKey), (89, payer_note: (String, WithoutLength)), @@ -421,9 +421,11 @@ impl TryFrom> for InvoiceRequest { (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream) )?; - if let Some(signature) = &signature { - merkle::verify_signature(signature, SIGNATURE_TAG, &bytes, contents.payer_id)?; - } + let signature = match signature { + None => return Err(ParseError::InvalidSemantics(SemanticError::MissingSignature)), + Some(signature) => signature, + }; + merkle::verify_signature(&signature, SIGNATURE_TAG, &bytes, contents.payer_id)?; Ok(InvoiceRequest { bytes, contents, signature }) } @@ -471,7 +473,7 @@ impl TryFrom for InvoiceRequestContents { #[cfg(test)] mod tests { - use super::InvoiceRequest; + use super::{InvoiceRequest, InvoiceRequestTlvStreamRef, SIGNATURE_TAG}; use bitcoin::blockdata::constants::ChainHash; use bitcoin::network::constants::Network; @@ -483,9 +485,10 @@ mod tests { use core::time::Duration; use crate::ln::features::InvoiceRequestFeatures; use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT}; - use crate::offers::merkle::SignError; - use crate::offers::offer::{Amount, OfferBuilder, Quantity}; + use crate::offers::merkle::{SignError, SignatureTlvStreamRef, self}; + use crate::offers::offer::{Amount, OfferBuilder, OfferTlvStreamRef, Quantity}; use crate::offers::parse::{ParseError, SemanticError}; + use crate::offers::payer::PayerTlvStreamRef; use crate::util::ser::{BigSize, Writeable}; use crate::util::string::PrintableString; @@ -517,14 +520,13 @@ mod tests { #[test] fn builds_invoice_request_with_defaults() { - let offer = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) .amount_msats(1000) - .build().unwrap(); - let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap().sign(payer_sign).unwrap(); + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .build().unwrap() + .sign(payer_sign).unwrap(); - let (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, signature_tlv_stream) = - invoice_request.as_tlv_stream(); let mut buffer = Vec::new(); invoice_request.write(&mut buffer).unwrap(); @@ -536,27 +538,40 @@ mod tests { assert_eq!(invoice_request.quantity(), None); assert_eq!(invoice_request.payer_id(), payer_pubkey()); assert_eq!(invoice_request.payer_note(), None); - assert!(invoice_request.signature().is_some()); - - assert_eq!(payer_tlv_stream.metadata, Some(&vec![1; 32])); - assert_eq!(offer_tlv_stream.chains, None); - assert_eq!(offer_tlv_stream.metadata, None); - assert_eq!(offer_tlv_stream.currency, None); - assert_eq!(offer_tlv_stream.amount, Some(1000)); - assert_eq!(offer_tlv_stream.description, Some(&String::from("foo"))); - assert_eq!(offer_tlv_stream.features, None); - assert_eq!(offer_tlv_stream.absolute_expiry, None); - assert_eq!(offer_tlv_stream.paths, None); - assert_eq!(offer_tlv_stream.issuer, None); - assert_eq!(offer_tlv_stream.quantity_max, None); - assert_eq!(offer_tlv_stream.node_id, Some(&recipient_pubkey())); - assert_eq!(invoice_request_tlv_stream.chain, None); - assert_eq!(invoice_request_tlv_stream.amount, None); - assert_eq!(invoice_request_tlv_stream.features, None); - assert_eq!(invoice_request_tlv_stream.quantity, None); - assert_eq!(invoice_request_tlv_stream.payer_id, Some(&payer_pubkey())); - assert_eq!(invoice_request_tlv_stream.payer_note, None); - assert!(signature_tlv_stream.signature.is_some()); + assert!( + merkle::verify_signature( + &invoice_request.signature, SIGNATURE_TAG, &invoice_request.bytes, payer_pubkey() + ).is_ok() + ); + + assert_eq!( + invoice_request.as_tlv_stream(), + ( + PayerTlvStreamRef { metadata: Some(&vec![1; 32]) }, + OfferTlvStreamRef { + chains: None, + metadata: None, + currency: None, + amount: Some(1000), + description: Some(&String::from("foo")), + features: None, + absolute_expiry: None, + paths: None, + issuer: None, + quantity_max: None, + node_id: Some(&recipient_pubkey()), + }, + InvoiceRequestTlvStreamRef { + chain: None, + amount: None, + features: None, + quantity: None, + payer_id: Some(&payer_pubkey()), + payer_note: None, + }, + SignatureTlvStreamRef { signature: Some(&invoice_request.signature()) }, + ), + ); if let Err(e) = InvoiceRequest::try_from(buffer) { panic!("error parsing invoice request: {:?}", e); @@ -1213,7 +1228,7 @@ mod tests { } #[test] - fn parses_invoice_request_without_signature() { + fn fails_parsing_invoice_request_without_signature() { let mut buffer = Vec::new(); OfferBuilder::new("foo".into(), recipient_pubkey()) .amount_msats(1000) @@ -1223,8 +1238,9 @@ mod tests { .invoice_request .write(&mut buffer).unwrap(); - if let Err(e) = InvoiceRequest::try_from(buffer) { - panic!("error parsing invoice_request: {:?}", e); + match InvoiceRequest::try_from(buffer) { + Ok(_) => panic!("expected error"), + Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingSignature)), } }