mirror of
https://github.com/lightningdevkit/rust-lightning.git
synced 2025-02-24 23:08:36 +01:00
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:
parent
b9970ff41b
commit
ac6d4cb9ad
4 changed files with 65 additions and 33 deletions
|
@ -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!(
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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!(
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Add table
Reference in a new issue