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.
This commit is contained in:
Jeffrey Czyz 2024-03-26 18:39:06 -05:00
parent b9970ff41b
commit ac6d4cb9ad
No known key found for this signature in database
GPG key ID: 912EF12EA67705F5
4 changed files with 65 additions and 33 deletions

View 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!(

View 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);

View 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!(

View 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();