Support signing BOLT 12 invoices in NodeSigner
[rust-lightning] / lightning / src / offers / invoice_request.rs
index 1dea6503f58fd23913874d0aa33c234cc3d50bce..03af068d1d61912738f23ac71ff3c91079161ffe 100644 (file)
@@ -344,18 +344,23 @@ impl<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signing> InvoiceRequestBuilder<'a
 }
 
 /// A semantically valid [`InvoiceRequest`] that hasn't been signed.
+///
+/// # Serialization
+///
+/// This is serialized as a TLV stream, which includes TLV records from the originating message. As
+/// such, it may include unknown, odd TLV records.
 pub struct UnsignedInvoiceRequest {
        bytes: Vec<u8>,
-       invoice_request: InvoiceRequestContents,
+       contents: InvoiceRequestContents,
        tagged_hash: TaggedHash,
 }
 
 impl UnsignedInvoiceRequest {
-       fn new(offer: &Offer, invoice_request: InvoiceRequestContents) -> Self {
+       fn new(offer: &Offer, contents: InvoiceRequestContents) -> Self {
                // Use the offer bytes instead of the offer TLV stream as the offer may have contained
                // unknown TLV records, which are not stored in `OfferContents`.
                let (payer_tlv_stream, _offer_tlv_stream, invoice_request_tlv_stream) =
-                       invoice_request.as_tlv_stream();
+                       contents.as_tlv_stream();
                let offer_bytes = WithoutLength(&offer.bytes);
                let unsigned_tlv_stream = (payer_tlv_stream, offer_bytes, invoice_request_tlv_stream);
 
@@ -364,17 +369,24 @@ impl UnsignedInvoiceRequest {
 
                let tagged_hash = TaggedHash::new(SIGNATURE_TAG, &bytes);
 
-               Self { bytes, invoice_request, tagged_hash }
+               Self { bytes, contents, tagged_hash }
        }
 
-       /// Signs the invoice request using the given function.
+       /// Returns the [`TaggedHash`] of the invoice to sign.
+       pub fn tagged_hash(&self) -> &TaggedHash {
+               &self.tagged_hash
+       }
+
+       /// Signs the [`TaggedHash`] of the invoice request using the given function.
+       ///
+       /// Note: The hash computation may have included unknown, odd TLV records.
        ///
        /// This is not exported to bindings users as functions are not yet mapped.
        pub fn sign<F, E>(mut self, sign: F) -> Result<InvoiceRequest, SignError<E>>
        where
                F: FnOnce(&Self) -> Result<Signature, E>
        {
-               let pubkey = self.invoice_request.payer_id;
+               let pubkey = self.contents.payer_id;
                let signature = merkle::sign_message(sign, &self, pubkey)?;
 
                // Append the signature TLV record to the bytes.
@@ -385,7 +397,7 @@ impl UnsignedInvoiceRequest {
 
                Ok(InvoiceRequest {
                        bytes: self.bytes,
-                       contents: self.invoice_request,
+                       contents: self.contents,
                        signature,
                })
        }
@@ -434,49 +446,58 @@ pub(super) struct InvoiceRequestContentsWithoutPayerId {
        payer_note: Option<String>,
 }
 
-impl InvoiceRequest {
+macro_rules! invoice_request_accessors { ($self: ident, $contents: expr) => {
        /// An unpredictable series of bytes, typically containing information about the derivation of
        /// [`payer_id`].
        ///
        /// [`payer_id`]: Self::payer_id
-       pub fn metadata(&self) -> &[u8] {
-               self.contents.metadata()
+       pub fn payer_metadata(&$self) -> &[u8] {
+               $contents.metadata()
        }
 
        /// A chain from [`Offer::chains`] that the offer is valid for.
-       pub fn chain(&self) -> ChainHash {
-               self.contents.chain()
+       pub fn chain(&$self) -> ChainHash {
+               $contents.chain()
        }
 
        /// The amount to pay in msats (i.e., the minimum lightning-payable unit for [`chain`]), which
        /// must be greater than or equal to [`Offer::amount`], converted if necessary.
        ///
        /// [`chain`]: Self::chain
-       pub fn amount_msats(&self) -> Option<u64> {
-               self.contents.inner.amount_msats
+       pub fn amount_msats(&$self) -> Option<u64> {
+               $contents.amount_msats()
        }
 
        /// Features pertaining to requesting an invoice.
-       pub fn features(&self) -> &InvoiceRequestFeatures {
-               &self.contents.inner.features
+       pub fn invoice_request_features(&$self) -> &InvoiceRequestFeatures {
+               &$contents.features()
        }
 
        /// The quantity of the offer's item conforming to [`Offer::is_valid_quantity`].
-       pub fn quantity(&self) -> Option<u64> {
-               self.contents.inner.quantity
+       pub fn quantity(&$self) -> Option<u64> {
+               $contents.quantity()
        }
 
        /// A possibly transient pubkey used to sign the invoice request.
-       pub fn payer_id(&self) -> PublicKey {
-               self.contents.payer_id
+       pub fn payer_id(&$self) -> PublicKey {
+               $contents.payer_id()
        }
 
        /// A payer-provided note which will be seen by the recipient and reflected back in the invoice
        /// response.
-       pub fn payer_note(&self) -> Option<PrintableString> {
-               self.contents.inner.payer_note.as_ref()
-                       .map(|payer_note| PrintableString(payer_note.as_str()))
+       pub fn payer_note(&$self) -> Option<PrintableString> {
+               $contents.payer_note()
        }
+} }
+
+impl UnsignedInvoiceRequest {
+       offer_accessors!(self, self.contents.inner.offer);
+       invoice_request_accessors!(self, self.contents);
+}
+
+impl InvoiceRequest {
+       offer_accessors!(self, self.contents.inner.offer);
+       invoice_request_accessors!(self, self.contents);
 
        /// Signature of the invoice request using [`payer_id`].
        ///
@@ -528,7 +549,7 @@ impl InvoiceRequest {
                &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
                created_at: core::time::Duration
        ) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, Bolt12SemanticError> {
-               if self.features().requires_unknown_bits() {
+               if self.invoice_request_features().requires_unknown_bits() {
                        return Err(Bolt12SemanticError::UnknownRequiredFeatures);
                }
 
@@ -571,7 +592,7 @@ impl InvoiceRequest {
                &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
                created_at: core::time::Duration, expanded_key: &ExpandedKey, secp_ctx: &Secp256k1<T>
        ) -> Result<InvoiceBuilder<DerivedSigningPubkey>, Bolt12SemanticError> {
-               if self.features().requires_unknown_bits() {
+               if self.invoice_request_features().requires_unknown_bits() {
                        return Err(Bolt12SemanticError::UnknownRequiredFeatures);
                }
 
@@ -619,10 +640,27 @@ impl InvoiceRequestContents {
                self.inner.chain()
        }
 
+       pub(super) fn amount_msats(&self) -> Option<u64> {
+               self.inner.amount_msats
+       }
+
+       pub(super) fn features(&self) -> &InvoiceRequestFeatures {
+               &self.inner.features
+       }
+
+       pub(super) fn quantity(&self) -> Option<u64> {
+               self.inner.quantity
+       }
+
        pub(super) fn payer_id(&self) -> PublicKey {
                self.payer_id
        }
 
+       pub(super) fn payer_note(&self) -> Option<PrintableString> {
+               self.inner.payer_note.as_ref()
+                       .map(|payer_note| PrintableString(payer_note.as_str()))
+       }
+
        pub(super) fn as_tlv_stream(&self) -> PartialInvoiceRequestTlvStreamRef {
                let (payer, offer, mut invoice_request) = self.inner.as_tlv_stream();
                invoice_request.payer_id = Some(&self.payer_id);
@@ -664,6 +702,12 @@ impl InvoiceRequestContentsWithoutPayerId {
        }
 }
 
+impl Writeable for UnsignedInvoiceRequest {
+       fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
+               WithoutLength(&self.bytes).write(writer)
+       }
+}
+
 impl Writeable for InvoiceRequest {
        fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
                WithoutLength(&self.bytes).write(writer)
@@ -723,6 +767,25 @@ type PartialInvoiceRequestTlvStreamRef<'a> = (
        InvoiceRequestTlvStreamRef<'a>,
 );
 
+impl TryFrom<Vec<u8>> for UnsignedInvoiceRequest {
+       type Error = Bolt12ParseError;
+
+       fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
+               let invoice_request = ParsedMessage::<PartialInvoiceRequestTlvStream>::try_from(bytes)?;
+               let ParsedMessage { bytes, tlv_stream } = invoice_request;
+               let (
+                       payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream,
+               ) = tlv_stream;
+               let contents = InvoiceRequestContents::try_from(
+                       (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream)
+               )?;
+
+               let tagged_hash = TaggedHash::new(SIGNATURE_TAG, &bytes);
+
+               Ok(UnsignedInvoiceRequest { bytes, contents, tagged_hash })
+       }
+}
+
 impl TryFrom<Vec<u8>> for InvoiceRequest {
        type Error = Bolt12ParseError;
 
@@ -741,7 +804,8 @@ impl TryFrom<Vec<u8>> for InvoiceRequest {
                        None => return Err(Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingSignature)),
                        Some(signature) => signature,
                };
-               merkle::verify_signature(&signature, SIGNATURE_TAG, &bytes, contents.payer_id)?;
+               let message = TaggedHash::new(SIGNATURE_TAG, &bytes);
+               merkle::verify_signature(&signature, message, contents.payer_id)?;
 
                Ok(InvoiceRequest { bytes, contents, signature })
        }
@@ -792,7 +856,7 @@ impl TryFrom<PartialInvoiceRequestTlvStream> for InvoiceRequestContents {
 
 #[cfg(test)]
 mod tests {
-       use super::{InvoiceRequest, InvoiceRequestTlvStreamRef, SIGNATURE_TAG};
+       use super::{InvoiceRequest, InvoiceRequestTlvStreamRef, SIGNATURE_TAG, UnsignedInvoiceRequest};
 
        use bitcoin::blockdata::constants::ChainHash;
        use bitcoin::network::constants::Network;
@@ -802,7 +866,7 @@ mod tests {
        #[cfg(feature = "std")]
        use core::time::Duration;
        use crate::sign::KeyMaterial;
-       use crate::ln::features::InvoiceRequestFeatures;
+       use crate::ln::features::{InvoiceRequestFeatures, OfferFeatures};
        use crate::ln::inbound_payment::ExpandedKey;
        use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
        use crate::offers::invoice::{Bolt12Invoice, SIGNATURE_TAG as INVOICE_SIGNATURE_TAG};
@@ -816,29 +880,68 @@ mod tests {
 
        #[test]
        fn builds_invoice_request_with_defaults() {
-               let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
+               let unsigned_invoice_request = 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();
+                       .build().unwrap();
+
+               let mut buffer = Vec::new();
+               unsigned_invoice_request.write(&mut buffer).unwrap();
+
+               assert_eq!(unsigned_invoice_request.bytes, buffer.as_slice());
+               assert_eq!(unsigned_invoice_request.payer_metadata(), &[1; 32]);
+               assert_eq!(unsigned_invoice_request.chains(), vec![ChainHash::using_genesis_block(Network::Bitcoin)]);
+               assert_eq!(unsigned_invoice_request.metadata(), None);
+               assert_eq!(unsigned_invoice_request.amount(), Some(&Amount::Bitcoin { amount_msats: 1000 }));
+               assert_eq!(unsigned_invoice_request.description(), PrintableString("foo"));
+               assert_eq!(unsigned_invoice_request.offer_features(), &OfferFeatures::empty());
+               assert_eq!(unsigned_invoice_request.absolute_expiry(), None);
+               assert_eq!(unsigned_invoice_request.paths(), &[]);
+               assert_eq!(unsigned_invoice_request.issuer(), None);
+               assert_eq!(unsigned_invoice_request.supported_quantity(), Quantity::One);
+               assert_eq!(unsigned_invoice_request.signing_pubkey(), recipient_pubkey());
+               assert_eq!(unsigned_invoice_request.chain(), ChainHash::using_genesis_block(Network::Bitcoin));
+               assert_eq!(unsigned_invoice_request.amount_msats(), None);
+               assert_eq!(unsigned_invoice_request.invoice_request_features(), &InvoiceRequestFeatures::empty());
+               assert_eq!(unsigned_invoice_request.quantity(), None);
+               assert_eq!(unsigned_invoice_request.payer_id(), payer_pubkey());
+               assert_eq!(unsigned_invoice_request.payer_note(), None);
+
+               match UnsignedInvoiceRequest::try_from(buffer) {
+                       Err(e) => panic!("error parsing unsigned invoice request: {:?}", e),
+                       Ok(parsed) => {
+                               assert_eq!(parsed.bytes, unsigned_invoice_request.bytes);
+                               assert_eq!(parsed.tagged_hash, unsigned_invoice_request.tagged_hash);
+                       },
+               }
+
+               let invoice_request = unsigned_invoice_request.sign(payer_sign).unwrap();
 
                let mut buffer = Vec::new();
                invoice_request.write(&mut buffer).unwrap();
 
                assert_eq!(invoice_request.bytes, buffer.as_slice());
-               assert_eq!(invoice_request.metadata(), &[1; 32]);
+               assert_eq!(invoice_request.payer_metadata(), &[1; 32]);
+               assert_eq!(invoice_request.chains(), vec![ChainHash::using_genesis_block(Network::Bitcoin)]);
+               assert_eq!(invoice_request.metadata(), None);
+               assert_eq!(invoice_request.amount(), Some(&Amount::Bitcoin { amount_msats: 1000 }));
+               assert_eq!(invoice_request.description(), PrintableString("foo"));
+               assert_eq!(invoice_request.offer_features(), &OfferFeatures::empty());
+               assert_eq!(invoice_request.absolute_expiry(), None);
+               assert_eq!(invoice_request.paths(), &[]);
+               assert_eq!(invoice_request.issuer(), None);
+               assert_eq!(invoice_request.supported_quantity(), Quantity::One);
+               assert_eq!(invoice_request.signing_pubkey(), recipient_pubkey());
                assert_eq!(invoice_request.chain(), ChainHash::using_genesis_block(Network::Bitcoin));
                assert_eq!(invoice_request.amount_msats(), None);
-               assert_eq!(invoice_request.features(), &InvoiceRequestFeatures::empty());
+               assert_eq!(invoice_request.invoice_request_features(), &InvoiceRequestFeatures::empty());
                assert_eq!(invoice_request.quantity(), None);
                assert_eq!(invoice_request.payer_id(), payer_pubkey());
                assert_eq!(invoice_request.payer_note(), None);
-               assert!(
-                       merkle::verify_signature(
-                               &invoice_request.signature, SIGNATURE_TAG, &invoice_request.bytes, payer_pubkey()
-                       ).is_ok()
-               );
+
+               let message = TaggedHash::new(SIGNATURE_TAG, &invoice_request.bytes);
+               assert!(merkle::verify_signature(&invoice_request.signature, message, payer_pubkey()).is_ok());
 
                assert_eq!(
                        invoice_request.as_tlv_stream(),
@@ -1231,7 +1334,7 @@ mod tests {
                        .build().unwrap()
                        .sign(payer_sign).unwrap();
                let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream();
-               assert_eq!(invoice_request.features(), &InvoiceRequestFeatures::unknown());
+               assert_eq!(invoice_request.invoice_request_features(), &InvoiceRequestFeatures::unknown());
                assert_eq!(tlv_stream.features, Some(&InvoiceRequestFeatures::unknown()));
 
                let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
@@ -1243,7 +1346,7 @@ mod tests {
                        .build().unwrap()
                        .sign(payer_sign).unwrap();
                let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream();
-               assert_eq!(invoice_request.features(), &InvoiceRequestFeatures::empty());
+               assert_eq!(invoice_request.invoice_request_features(), &InvoiceRequestFeatures::empty());
                assert_eq!(tlv_stream.features, None);
        }
 
@@ -1681,7 +1784,7 @@ mod tests {
                        .build().unwrap();
                let unsigned_invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
                        .build().unwrap();
-               let mut tlv_stream = unsigned_invoice_request.invoice_request.as_tlv_stream();
+               let mut tlv_stream = unsigned_invoice_request.contents.as_tlv_stream();
                tlv_stream.0.metadata = None;
 
                let mut buffer = Vec::new();
@@ -1702,7 +1805,7 @@ mod tests {
                        .build().unwrap();
                let unsigned_invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
                        .build().unwrap();
-               let mut tlv_stream = unsigned_invoice_request.invoice_request.as_tlv_stream();
+               let mut tlv_stream = unsigned_invoice_request.contents.as_tlv_stream();
                tlv_stream.2.payer_id = None;
 
                let mut buffer = Vec::new();
@@ -1721,7 +1824,7 @@ mod tests {
                        .build().unwrap();
                let unsigned_invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
                        .build().unwrap();
-               let mut tlv_stream = unsigned_invoice_request.invoice_request.as_tlv_stream();
+               let mut tlv_stream = unsigned_invoice_request.contents.as_tlv_stream();
                tlv_stream.1.node_id = None;
 
                let mut buffer = Vec::new();
@@ -1743,7 +1846,7 @@ mod tests {
                        .build().unwrap()
                        .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
                        .build().unwrap()
-                       .invoice_request
+                       .contents
                        .write(&mut buffer).unwrap();
 
                match InvoiceRequest::try_from(buffer) {