Include OfferId in VerifiedInvoiceRequest
authorJeffrey Czyz <jkczyz@gmail.com>
Tue, 26 Mar 2024 23:39:06 +0000 (18:39 -0500)
committerJeffrey Czyz <jkczyz@gmail.com>
Thu, 18 Apr 2024 14:15:21 +0000 (09:15 -0500)
Extract the OfferId from the offer metadata sent back in the
InvoiceRequest and include it in VerifiedInvoiceRequest. This can be
used to correspond the eventual payment for the invoice response by
including it in the invoice's blinded payment paths.

lightning/src/offers/invoice.rs
lightning/src/offers/invoice_request.rs
lightning/src/offers/merkle.rs
lightning/src/offers/offer.rs

index a384ab0865659ecb95d052dcdeb834932851057b..ee5e6deb4086bb69992015a053efdffab89c8816 100644 (file)
@@ -546,7 +546,7 @@ impl UnsignedBolt12Invoice {
                let mut bytes = Vec::new();
                unsigned_tlv_stream.write(&mut bytes).unwrap();
 
-               let tagged_hash = TaggedHash::new(SIGNATURE_TAG, &bytes);
+               let tagged_hash = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &bytes);
 
                Self { bytes, contents, tagged_hash }
        }
@@ -1225,7 +1225,7 @@ impl TryFrom<Vec<u8>> for UnsignedBolt12Invoice {
                        (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream)
                )?;
 
-               let tagged_hash = TaggedHash::new(SIGNATURE_TAG, &bytes);
+               let tagged_hash = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &bytes);
 
                Ok(UnsignedBolt12Invoice { bytes, contents, tagged_hash })
        }
@@ -1370,7 +1370,7 @@ impl TryFrom<ParsedMessage<FullInvoiceTlvStream>> for Bolt12Invoice {
                        None => return Err(Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingSignature)),
                        Some(signature) => signature,
                };
-               let tagged_hash = TaggedHash::new(SIGNATURE_TAG, &bytes);
+               let tagged_hash = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &bytes);
                let pubkey = contents.fields().signing_pubkey;
                merkle::verify_signature(&signature, &tagged_hash, pubkey)?;
 
@@ -1601,7 +1601,7 @@ mod tests {
                assert_eq!(invoice.invoice_features(), &Bolt12InvoiceFeatures::empty());
                assert_eq!(invoice.signing_pubkey(), recipient_pubkey());
 
-               let message = TaggedHash::new(SIGNATURE_TAG, &invoice.bytes);
+               let message = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &invoice.bytes);
                assert!(merkle::verify_signature(&invoice.signature, &message, recipient_pubkey()).is_ok());
 
                let digest = Message::from_slice(&invoice.signable_hash()).unwrap();
@@ -1698,7 +1698,7 @@ mod tests {
                assert_eq!(invoice.invoice_features(), &Bolt12InvoiceFeatures::empty());
                assert_eq!(invoice.signing_pubkey(), recipient_pubkey());
 
-               let message = TaggedHash::new(SIGNATURE_TAG, &invoice.bytes);
+               let message = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &invoice.bytes);
                assert!(merkle::verify_signature(&invoice.signature, &message, recipient_pubkey()).is_ok());
 
                assert_eq!(
index 5e3ed40ac08db2b2a8a662c357bb4eb475fb45fc..07a117233fa2eb2a1dd181506008e4e3b976ab4a 100644 (file)
@@ -72,7 +72,7 @@ use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce};
 use crate::ln::msgs::DecodeError;
 use crate::offers::invoice::BlindedPayInfo;
 use crate::offers::merkle::{SignError, SignFn, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, self};
-use crate::offers::offer::{Offer, OfferContents, OfferTlvStream, OfferTlvStreamRef};
+use crate::offers::offer::{Offer, OfferContents, OfferId, OfferTlvStream, OfferTlvStreamRef};
 use crate::offers::parse::{Bolt12ParseError, ParsedMessage, Bolt12SemanticError};
 use crate::offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef};
 use crate::offers::signer::{Metadata, MetadataMaterial};
@@ -529,7 +529,7 @@ impl UnsignedInvoiceRequest {
                let mut bytes = Vec::new();
                unsigned_tlv_stream.write(&mut bytes).unwrap();
 
-               let tagged_hash = TaggedHash::new(SIGNATURE_TAG, &bytes);
+               let tagged_hash = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &bytes);
 
                Self { bytes, contents, tagged_hash }
        }
@@ -607,6 +607,9 @@ pub struct InvoiceRequest {
 /// ways to respond depending on whether the signing keys were derived.
 #[derive(Clone, Debug)]
 pub struct VerifiedInvoiceRequest {
+       /// The identifier of the [`Offer`] for which the [`InvoiceRequest`] was made.
+       pub offer_id: OfferId,
+
        /// The verified request.
        inner: InvoiceRequest,
 
@@ -764,8 +767,9 @@ macro_rules! invoice_request_verify_method { ($self: ident, $self_type: ty) => {
                #[cfg(c_bindings)]
                secp_ctx: &Secp256k1<secp256k1::All>,
        ) -> Result<VerifiedInvoiceRequest, ()> {
-               let keys = $self.contents.inner.offer.verify(&$self.bytes, key, secp_ctx)?;
+               let (offer_id, keys) = $self.contents.inner.offer.verify(&$self.bytes, key, secp_ctx)?;
                Ok(VerifiedInvoiceRequest {
+                       offer_id,
                        #[cfg(not(c_bindings))]
                        inner: $self,
                        #[cfg(c_bindings)]
@@ -1022,7 +1026,7 @@ impl TryFrom<Vec<u8>> for UnsignedInvoiceRequest {
                        (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream)
                )?;
 
-               let tagged_hash = TaggedHash::new(SIGNATURE_TAG, &bytes);
+               let tagged_hash = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &bytes);
 
                Ok(UnsignedInvoiceRequest { bytes, contents, tagged_hash })
        }
@@ -1046,7 +1050,7 @@ impl TryFrom<Vec<u8>> for InvoiceRequest {
                        None => return Err(Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingSignature)),
                        Some(signature) => signature,
                };
-               let message = TaggedHash::new(SIGNATURE_TAG, &bytes);
+               let message = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &bytes);
                merkle::verify_signature(&signature, &message, contents.payer_id)?;
 
                Ok(InvoiceRequest { bytes, contents, signature })
@@ -1192,7 +1196,7 @@ mod tests {
                assert_eq!(invoice_request.payer_id(), payer_pubkey());
                assert_eq!(invoice_request.payer_note(), None);
 
-               let message = TaggedHash::new(SIGNATURE_TAG, &invoice_request.bytes);
+               let message = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &invoice_request.bytes);
                assert!(merkle::verify_signature(&invoice_request.signature, &message, payer_pubkey()).is_ok());
 
                assert_eq!(
@@ -1297,7 +1301,7 @@ mod tests {
                let mut bytes = Vec::new();
                tlv_stream.write(&mut bytes).unwrap();
 
-               let message = TaggedHash::new(INVOICE_SIGNATURE_TAG, &bytes);
+               let message = TaggedHash::from_valid_tlv_stream_bytes(INVOICE_SIGNATURE_TAG, &bytes);
                let signature = merkle::sign_message(recipient_sign, &message, recipient_pubkey()).unwrap();
                signature_tlv_stream.signature = Some(&signature);
 
@@ -1320,7 +1324,7 @@ mod tests {
                let mut bytes = Vec::new();
                tlv_stream.write(&mut bytes).unwrap();
 
-               let message = TaggedHash::new(INVOICE_SIGNATURE_TAG, &bytes);
+               let message = TaggedHash::from_valid_tlv_stream_bytes(INVOICE_SIGNATURE_TAG, &bytes);
                let signature = merkle::sign_message(recipient_sign, &message, recipient_pubkey()).unwrap();
                signature_tlv_stream.signature = Some(&signature);
 
@@ -1369,7 +1373,7 @@ mod tests {
                let mut bytes = Vec::new();
                tlv_stream.write(&mut bytes).unwrap();
 
-               let message = TaggedHash::new(INVOICE_SIGNATURE_TAG, &bytes);
+               let message = TaggedHash::from_valid_tlv_stream_bytes(INVOICE_SIGNATURE_TAG, &bytes);
                let signature = merkle::sign_message(recipient_sign, &message, recipient_pubkey()).unwrap();
                signature_tlv_stream.signature = Some(&signature);
 
@@ -1392,7 +1396,7 @@ mod tests {
                let mut bytes = Vec::new();
                tlv_stream.write(&mut bytes).unwrap();
 
-               let message = TaggedHash::new(INVOICE_SIGNATURE_TAG, &bytes);
+               let message = TaggedHash::from_valid_tlv_stream_bytes(INVOICE_SIGNATURE_TAG, &bytes);
                let signature = merkle::sign_message(recipient_sign, &message, recipient_pubkey()).unwrap();
                signature_tlv_stream.signature = Some(&signature);
 
index 9dd6bcde8ca360286c093cab2e11645f866e5d19..a3979866926170e8b5dc467ef10c59aae9c91c54 100644 (file)
@@ -38,10 +38,20 @@ pub struct TaggedHash {
 }
 
 impl TaggedHash {
+       /// Creates a tagged hash with the given parameters.
+       ///
+       /// Panics if `bytes` is not a well-formed TLV stream containing at least one TLV record.
+       pub(super) fn from_valid_tlv_stream_bytes(tag: &'static str, bytes: &[u8]) -> Self {
+               let tlv_stream = TlvStream::new(bytes);
+               Self::from_tlv_stream(tag, tlv_stream)
+       }
+
        /// Creates a tagged hash with the given parameters.
        ///
        /// Panics if `tlv_stream` is not a well-formed TLV stream containing at least one TLV record.
-       pub(super) fn new(tag: &'static str, tlv_stream: &[u8]) -> Self {
+       pub(super) fn from_tlv_stream<'a, I: core::iter::Iterator<Item = TlvRecord<'a>>>(
+               tag: &'static str, tlv_stream: I
+       ) -> Self {
                let tag_hash = sha256::Hash::hash(tag.as_bytes());
                let merkle_root = root_hash(tlv_stream);
                let digest = Message::from_slice(tagged_hash(tag_hash, merkle_root).as_byte_array()).unwrap();
@@ -141,9 +151,10 @@ pub(super) fn verify_signature(
 
 /// Computes a merkle root hash for the given data, which must be a well-formed TLV stream
 /// containing at least one TLV record.
-fn root_hash(data: &[u8]) -> sha256::Hash {
+fn root_hash<'a, I: core::iter::Iterator<Item = TlvRecord<'a>>>(tlv_stream: I) -> sha256::Hash {
+       let mut tlv_stream = tlv_stream.peekable();
        let nonce_tag = tagged_hash_engine(sha256::Hash::from_engine({
-               let first_tlv_record = TlvStream::new(&data[..]).next().unwrap();
+               let first_tlv_record = tlv_stream.peek().unwrap();
                let mut engine = sha256::Hash::engine();
                engine.input("LnNonce".as_bytes());
                engine.input(first_tlv_record.record_bytes);
@@ -153,8 +164,7 @@ fn root_hash(data: &[u8]) -> sha256::Hash {
        let branch_tag = tagged_hash_engine(sha256::Hash::hash("LnBranch".as_bytes()));
 
        let mut leaves = Vec::new();
-       let tlv_stream = TlvStream::new(&data[..]);
-       for record in tlv_stream.skip_signatures() {
+       for record in TlvStream::skip_signatures(tlv_stream) {
                leaves.push(tagged_hash_from_engine(leaf_tag.clone(), &record.record_bytes));
                leaves.push(tagged_hash_from_engine(nonce_tag.clone(), &record.type_bytes));
        }
@@ -231,8 +241,10 @@ impl<'a> TlvStream<'a> {
                        .take_while(move |record| take_range.contains(&record.r#type))
        }
 
-       fn skip_signatures(self) -> core::iter::Filter<TlvStream<'a>, fn(&TlvRecord) -> bool> {
-               self.filter(|record| !SIGNATURE_TYPES.contains(&record.r#type))
+       fn skip_signatures(
+               tlv_stream: impl core::iter::Iterator<Item = TlvRecord<'a>>
+       ) -> impl core::iter::Iterator<Item = TlvRecord<'a>> {
+               tlv_stream.filter(|record| !SIGNATURE_TYPES.contains(&record.r#type))
        }
 }
 
@@ -280,7 +292,7 @@ impl<'a> Writeable for WithoutSignatures<'a> {
        #[inline]
        fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
                let tlv_stream = TlvStream::new(self.0);
-               for record in tlv_stream.skip_signatures() {
+               for record in TlvStream::skip_signatures(tlv_stream) {
                        writer.write_all(record.record_bytes)?;
                }
                Ok(())
@@ -308,15 +320,15 @@ mod tests {
                macro_rules! tlv2 { () => { "02080000010000020003" } }
                macro_rules! tlv3 { () => { "03310266e4598d1d3c415f572a8488830b60f7e744ed9235eb0b1ba93283b315c0351800000000000000010000000000000002" } }
                assert_eq!(
-                       super::root_hash(&<Vec<u8>>::from_hex(tlv1!()).unwrap()),
+                       super::root_hash(TlvStream::new(&<Vec<u8>>::from_hex(tlv1!()).unwrap())),
                        sha256::Hash::from_slice(&<Vec<u8>>::from_hex("b013756c8fee86503a0b4abdab4cddeb1af5d344ca6fc2fa8b6c08938caa6f93").unwrap()).unwrap(),
                );
                assert_eq!(
-                       super::root_hash(&<Vec<u8>>::from_hex(concat!(tlv1!(), tlv2!())).unwrap()),
+                       super::root_hash(TlvStream::new(&<Vec<u8>>::from_hex(concat!(tlv1!(), tlv2!())).unwrap())),
                        sha256::Hash::from_slice(&<Vec<u8>>::from_hex("c3774abbf4815aa54ccaa026bff6581f01f3be5fe814c620a252534f434bc0d1").unwrap()).unwrap(),
                );
                assert_eq!(
-                       super::root_hash(&<Vec<u8>>::from_hex(concat!(tlv1!(), tlv2!(), tlv3!())).unwrap()),
+                       super::root_hash(TlvStream::new(&<Vec<u8>>::from_hex(concat!(tlv1!(), tlv2!(), tlv3!())).unwrap())),
                        sha256::Hash::from_slice(&<Vec<u8>>::from_hex("ab2e79b1283b0b31e0b035258de23782df6b89a38cfa7237bde69aed1a658c5d").unwrap()).unwrap(),
                );
        }
@@ -348,7 +360,7 @@ mod tests {
                        "lnr1qqyqqqqqqqqqqqqqqcp4256ypqqkgzshgysy6ct5dpjk6ct5d93kzmpq23ex2ct5d9ek293pqthvwfzadd7jejes8q9lhc4rvjxd022zv5l44g6qah82ru5rdpnpjkppqvjx204vgdzgsqpvcp4mldl3plscny0rt707gvpdh6ndydfacz43euzqhrurageg3n7kafgsek6gz3e9w52parv8gs2hlxzk95tzeswywffxlkeyhml0hh46kndmwf4m6xma3tkq2lu04qz3slje2rfthc89vss",
                );
                assert_eq!(
-                       super::root_hash(&invoice_request.bytes[..]),
+                       super::root_hash(TlvStream::new(&invoice_request.bytes[..])),
                        sha256::Hash::from_slice(&<Vec<u8>>::from_hex("608407c18ad9a94d9ea2bcdbe170b6c20c462a7833a197621c916f78cf18e624").unwrap()).unwrap(),
                );
                assert_eq!(
index df3df4b3f64218ac52caa31ee31a982aa31b444d..324e3ea42c94d378cd71020a36c1c97d63ee8348 100644 (file)
@@ -122,7 +122,13 @@ impl OfferId {
        const ID_TAG: &'static str = "LDK Offer ID";
 
        fn from_valid_offer_tlv_stream(bytes: &[u8]) -> Self {
-               let tagged_hash = TaggedHash::new(Self::ID_TAG, &bytes);
+               let tagged_hash = TaggedHash::from_valid_tlv_stream_bytes(Self::ID_TAG, bytes);
+               Self(tagged_hash.to_bytes())
+       }
+
+       fn from_valid_invreq_tlv_stream(bytes: &[u8]) -> Self {
+               let tlv_stream = TlvStream::new(bytes).range(OFFER_TYPES);
+               let tagged_hash = TaggedHash::from_tlv_stream(Self::ID_TAG, tlv_stream);
                Self(tagged_hash.to_bytes())
        }
 }
@@ -887,7 +893,7 @@ impl OfferContents {
        /// Verifies that the offer metadata was produced from the offer in the TLV stream.
        pub(super) fn verify<T: secp256k1::Signing>(
                &self, bytes: &[u8], key: &ExpandedKey, secp_ctx: &Secp256k1<T>
-       ) -> Result<Option<KeyPair>, ()> {
+       ) -> Result<(OfferId, Option<KeyPair>), ()> {
                match self.metadata() {
                        Some(metadata) => {
                                let tlv_stream = TlvStream::new(bytes).range(OFFER_TYPES).filter(|record| {
@@ -899,9 +905,13 @@ impl OfferContents {
                                                _ => true,
                                        }
                                });
-                               signer::verify_recipient_metadata(
+                               let keys = signer::verify_recipient_metadata(
                                        metadata, key, IV_BYTES, self.signing_pubkey(), tlv_stream, secp_ctx
-                               )
+                               )?;
+
+                               let offer_id = OfferId::from_valid_invreq_tlv_stream(bytes);
+
+                               Ok((offer_id, keys))
                        },
                        None => Err(()),
                }
@@ -1246,7 +1256,10 @@ mod tests {
                let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
                        .build().unwrap()
                        .sign(payer_sign).unwrap();
-               assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_ok());
+               match invoice_request.verify(&expanded_key, &secp_ctx) {
+                       Ok(invoice_request) => assert_eq!(invoice_request.offer_id, offer.id()),
+                       Err(_) => panic!("unexpected error"),
+               }
 
                // Fails verification with altered offer field
                let mut tlv_stream = offer.as_tlv_stream();
@@ -1305,7 +1318,10 @@ mod tests {
                let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
                        .build().unwrap()
                        .sign(payer_sign).unwrap();
-               assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_ok());
+               match invoice_request.verify(&expanded_key, &secp_ctx) {
+                       Ok(invoice_request) => assert_eq!(invoice_request.offer_id, offer.id()),
+                       Err(_) => panic!("unexpected error"),
+               }
 
                // Fails verification with altered offer field
                let mut tlv_stream = offer.as_tlv_stream();