Define an OfferId for BOLT 12 Offers

Use a merkle root hash of the offer TLV records to define an offer id.
Will be included in a BOLT 12 invoice's blinded payment paths in order
for the recipient to identify which offer the payment is for.
This commit is contained in:
Jeffrey Czyz 2024-03-25 16:10:36 -05:00
parent 9be364f60e
commit b9970ff41b
No known key found for this signature in database
GPG key ID: 912EF12EA67705F5
2 changed files with 45 additions and 5 deletions

View file

@ -66,6 +66,10 @@ impl TaggedHash {
pub fn merkle_root(&self) -> sha256::Hash { pub fn merkle_root(&self) -> sha256::Hash {
self.merkle_root self.merkle_root
} }
pub(super) fn to_bytes(&self) -> [u8; 32] {
*self.digest.as_ref()
}
} }
impl AsRef<TaggedHash> for TaggedHash { impl AsRef<TaggedHash> for TaggedHash {

View file

@ -90,11 +90,11 @@ use crate::blinded_path::BlindedPath;
use crate::ln::channelmanager::PaymentId; use crate::ln::channelmanager::PaymentId;
use crate::ln::features::OfferFeatures; use crate::ln::features::OfferFeatures;
use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce}; use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce};
use crate::ln::msgs::MAX_VALUE_MSAT; use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
use crate::offers::merkle::TlvStream; use crate::offers::merkle::{TaggedHash, TlvStream};
use crate::offers::parse::{Bech32Encode, Bolt12ParseError, Bolt12SemanticError, ParsedMessage}; use crate::offers::parse::{Bech32Encode, Bolt12ParseError, Bolt12SemanticError, ParsedMessage};
use crate::offers::signer::{Metadata, MetadataMaterial, self}; 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; use crate::util::string::PrintableString;
#[cfg(not(c_bindings))] #[cfg(not(c_bindings))]
@ -114,6 +114,31 @@ use std::time::SystemTime;
pub(super) const IV_BYTES: &[u8; IV_LEN] = b"LDK Offer ~~~~~~"; 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::new(Self::ID_TAG, &bytes);
Self(tagged_hash.to_bytes())
}
}
impl Writeable for OfferId {
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
self.0.write(w)
}
}
impl Readable for OfferId {
fn read<R: io::Read>(r: &mut R) -> Result<Self, DecodeError> {
Ok(OfferId(Readable::read(r)?))
}
}
/// Builds an [`Offer`] for the "offer to be paid" flow. /// Builds an [`Offer`] for the "offer to be paid" flow.
/// ///
/// See [module-level documentation] for usage. /// See [module-level documentation] for usage.
@ -370,12 +395,15 @@ macro_rules! offer_builder_methods { (
let mut bytes = Vec::new(); let mut bytes = Vec::new();
$self.offer.write(&mut bytes).unwrap(); $self.offer.write(&mut bytes).unwrap();
let id = OfferId::from_valid_offer_tlv_stream(&bytes);
Offer { Offer {
bytes, bytes,
#[cfg(not(c_bindings))] #[cfg(not(c_bindings))]
contents: $self.offer, contents: $self.offer,
#[cfg(c_bindings)] #[cfg(c_bindings)]
contents: $self.offer.clone() contents: $self.offer.clone(),
id,
} }
} }
} } } }
@ -488,6 +516,7 @@ pub struct Offer {
// fields. // fields.
pub(super) bytes: Vec<u8>, pub(super) bytes: Vec<u8>,
pub(super) contents: OfferContents, pub(super) contents: OfferContents,
id: OfferId,
} }
/// The contents of an [`Offer`], which may be shared with an [`InvoiceRequest`] or a /// The contents of an [`Offer`], which may be shared with an [`InvoiceRequest`] or a
@ -577,6 +606,11 @@ macro_rules! offer_accessors { ($self: ident, $contents: expr) => {
impl Offer { impl Offer {
offer_accessors!(self, self.contents); 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 { pub(super) fn implied_chain(&self) -> ChainHash {
self.contents.implied_chain() self.contents.implied_chain()
} }
@ -1002,7 +1036,9 @@ impl TryFrom<Vec<u8>> for Offer {
let offer = ParsedMessage::<OfferTlvStream>::try_from(bytes)?; let offer = ParsedMessage::<OfferTlvStream>::try_from(bytes)?;
let ParsedMessage { bytes, tlv_stream } = offer; let ParsedMessage { bytes, tlv_stream } = offer;
let contents = OfferContents::try_from(tlv_stream)?; 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 })
} }
} }