X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Foffers%2Foffer.rs;h=324e3ea42c94d378cd71020a36c1c97d63ee8348;hb=ac6d4cb9ad45189e963bd906e9da971502dff129;hp=f7b75138b5108bbecad7cae5faae4ede0d9749e5;hpb=eaf76f6cceefdc08202d7fe08e4760df0e824d38;p=rust-lightning diff --git a/lightning/src/offers/offer.rs b/lightning/src/offers/offer.rs index f7b75138..324e3ea4 100644 --- a/lightning/src/offers/offer.rs +++ b/lightning/src/offers/offer.rs @@ -90,11 +90,11 @@ use crate::blinded_path::BlindedPath; use crate::ln::channelmanager::PaymentId; use crate::ln::features::OfferFeatures; use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce}; -use crate::ln::msgs::MAX_VALUE_MSAT; -use crate::offers::merkle::TlvStream; +use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT}; +use crate::offers::merkle::{TaggedHash, TlvStream}; use crate::offers::parse::{Bech32Encode, Bolt12ParseError, Bolt12SemanticError, ParsedMessage}; use crate::offers::signer::{Metadata, MetadataMaterial, self}; -use crate::util::ser::{HighZeroBytesDroppedBigSize, WithoutLength, Writeable, Writer}; +use crate::util::ser::{HighZeroBytesDroppedBigSize, Readable, WithoutLength, Writeable, Writer}; use crate::util::string::PrintableString; #[cfg(not(c_bindings))] @@ -114,6 +114,37 @@ use std::time::SystemTime; pub(super) const IV_BYTES: &[u8; IV_LEN] = b"LDK Offer ~~~~~~"; +/// An identifier for an [`Offer`] built using [`DerivedMetadata`]. +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct OfferId(pub [u8; 32]); + +impl OfferId { + const ID_TAG: &'static str = "LDK Offer ID"; + + fn from_valid_offer_tlv_stream(bytes: &[u8]) -> Self { + 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()) + } +} + +impl Writeable for OfferId { + fn write(&self, w: &mut W) -> Result<(), io::Error> { + self.0.write(w) + } +} + +impl Readable for OfferId { + fn read(r: &mut R) -> Result { + Ok(OfferId(Readable::read(r)?)) + } +} + /// Builds an [`Offer`] for the "offer to be paid" flow. /// /// See [module-level documentation] for usage. @@ -370,12 +401,15 @@ macro_rules! offer_builder_methods { ( let mut bytes = Vec::new(); $self.offer.write(&mut bytes).unwrap(); + let id = OfferId::from_valid_offer_tlv_stream(&bytes); + Offer { bytes, #[cfg(not(c_bindings))] contents: $self.offer, #[cfg(c_bindings)] - contents: $self.offer.clone() + contents: $self.offer.clone(), + id, } } } } @@ -488,6 +522,7 @@ pub struct Offer { // fields. pub(super) bytes: Vec, pub(super) contents: OfferContents, + id: OfferId, } /// The contents of an [`Offer`], which may be shared with an [`InvoiceRequest`] or a @@ -577,6 +612,11 @@ macro_rules! offer_accessors { ($self: ident, $contents: expr) => { impl Offer { offer_accessors!(self, self.contents); + /// Returns the id of the offer. + pub fn id(&self) -> OfferId { + self.id + } + pub(super) fn implied_chain(&self) -> ChainHash { self.contents.implied_chain() } @@ -853,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| { @@ -865,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(()), } @@ -1002,7 +1046,9 @@ impl TryFrom> for Offer { let offer = ParsedMessage::::try_from(bytes)?; let ParsedMessage { bytes, tlv_stream } = offer; let contents = OfferContents::try_from(tlv_stream)?; - Ok(Offer { bytes, contents }) + let id = OfferId::from_valid_offer_tlv_stream(&bytes); + + Ok(Offer { bytes, contents, id }) } } @@ -1078,7 +1124,7 @@ mod tests { use bitcoin::secp256k1::Secp256k1; use core::num::NonZeroU64; use core::time::Duration; - use crate::blinded_path::{BlindedHop, BlindedPath}; + use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode}; use crate::sign::KeyMaterial; use crate::ln::features::OfferFeatures; use crate::ln::inbound_payment::ExpandedKey; @@ -1210,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(); @@ -1249,7 +1298,7 @@ mod tests { let secp_ctx = Secp256k1::new(); let blinded_path = BlindedPath { - introduction_node_id: pubkey(40), + introduction_node: IntroductionNode::NodeId(pubkey(40)), blinding_point: pubkey(41), blinded_hops: vec![ BlindedHop { blinded_node_id: pubkey(42), encrypted_payload: vec![0; 43] }, @@ -1269,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(); @@ -1395,7 +1447,7 @@ mod tests { fn builds_offer_with_paths() { let paths = vec![ BlindedPath { - introduction_node_id: pubkey(40), + introduction_node: IntroductionNode::NodeId(pubkey(40)), blinding_point: pubkey(41), blinded_hops: vec![ BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] }, @@ -1403,7 +1455,7 @@ mod tests { ], }, BlindedPath { - introduction_node_id: pubkey(40), + introduction_node: IntroductionNode::NodeId(pubkey(40)), blinding_point: pubkey(41), blinded_hops: vec![ BlindedHop { blinded_node_id: pubkey(45), encrypted_payload: vec![0; 45] }, @@ -1585,7 +1637,7 @@ mod tests { fn parses_offer_with_paths() { let offer = OfferBuilder::new("foo".into(), pubkey(42)) .path(BlindedPath { - introduction_node_id: pubkey(40), + introduction_node: IntroductionNode::NodeId(pubkey(40)), blinding_point: pubkey(41), blinded_hops: vec![ BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] }, @@ -1593,7 +1645,7 @@ mod tests { ], }) .path(BlindedPath { - introduction_node_id: pubkey(40), + introduction_node: IntroductionNode::NodeId(pubkey(40)), blinding_point: pubkey(41), blinded_hops: vec![ BlindedHop { blinded_node_id: pubkey(45), encrypted_payload: vec![0; 45] },