From ac6d4cb9ad45189e963bd906e9da971502dff129 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Tue, 26 Mar 2024 18:39:06 -0500 Subject: [PATCH] Include OfferId in VerifiedInvoiceRequest 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 | 10 +++---- lightning/src/offers/invoice_request.rs | 24 ++++++++++------- lightning/src/offers/merkle.rs | 36 ++++++++++++++++--------- lightning/src/offers/offer.rs | 28 ++++++++++++++----- 4 files changed, 65 insertions(+), 33 deletions(-) diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index a384ab086..ee5e6deb4 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -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> 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> 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!( diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index 5e3ed40ac..07a117233 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -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, ) -> Result { - 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> 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> 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); diff --git a/lightning/src/offers/merkle.rs b/lightning/src/offers/merkle.rs index 9dd6bcde8..a39798669 100644 --- a/lightning/src/offers/merkle.rs +++ b/lightning/src/offers/merkle.rs @@ -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>>( + 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>>(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, fn(&TlvRecord) -> bool> { - self.filter(|record| !SIGNATURE_TYPES.contains(&record.r#type)) + fn skip_signatures( + tlv_stream: impl core::iter::Iterator> + ) -> impl core::iter::Iterator> { + tlv_stream.filter(|record| !SIGNATURE_TYPES.contains(&record.r#type)) } } @@ -280,7 +292,7 @@ impl<'a> Writeable for WithoutSignatures<'a> { #[inline] fn write(&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(&>::from_hex(tlv1!()).unwrap()), + super::root_hash(TlvStream::new(&>::from_hex(tlv1!()).unwrap())), sha256::Hash::from_slice(&>::from_hex("b013756c8fee86503a0b4abdab4cddeb1af5d344ca6fc2fa8b6c08938caa6f93").unwrap()).unwrap(), ); assert_eq!( - super::root_hash(&>::from_hex(concat!(tlv1!(), tlv2!())).unwrap()), + super::root_hash(TlvStream::new(&>::from_hex(concat!(tlv1!(), tlv2!())).unwrap())), sha256::Hash::from_slice(&>::from_hex("c3774abbf4815aa54ccaa026bff6581f01f3be5fe814c620a252534f434bc0d1").unwrap()).unwrap(), ); assert_eq!( - super::root_hash(&>::from_hex(concat!(tlv1!(), tlv2!(), tlv3!())).unwrap()), + super::root_hash(TlvStream::new(&>::from_hex(concat!(tlv1!(), tlv2!(), tlv3!())).unwrap())), sha256::Hash::from_slice(&>::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(&>::from_hex("608407c18ad9a94d9ea2bcdbe170b6c20c462a7833a197621c916f78cf18e624").unwrap()).unwrap(), ); assert_eq!( diff --git a/lightning/src/offers/offer.rs b/lightning/src/offers/offer.rs index df3df4b3f..324e3ea42 100644 --- a/lightning/src/offers/offer.rs +++ b/lightning/src/offers/offer.rs @@ -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( &self, bytes: &[u8], key: &ExpandedKey, secp_ctx: &Secp256k1 - ) -> Result, ()> { + ) -> Result<(OfferId, Option), ()> { 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(); -- 2.39.5