Skip to content

Commit b8d6347

Browse files
committed
Refund metadata and payer id derivation
Add support for deriving a transient payer id for each Refund from an ExpandedKey and a nonce. This facilitates payer privacy by not tying any Refund to any other nor to the payer's node id. Additionally, support stateless Invoice verification by setting payer metadata using an HMAC over the nonce and the remaining TLV records, which will be later verified when receiving an Invoice response.
1 parent aed58c4 commit b8d6347

File tree

1 file changed

+61
-1
lines changed

1 file changed

+61
-1
lines changed

lightning/src/offers/refund.rs

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,14 @@ use core::time::Duration;
8080
use crate::io;
8181
use crate::ln::PaymentHash;
8282
use crate::ln::features::InvoiceRequestFeatures;
83+
use crate::ln::inbound_payment::{ExpandedKey, Nonce};
8384
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
8485
use crate::offers::invoice::{BlindedPayInfo, InvoiceBuilder};
8586
use crate::offers::invoice_request::{InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef};
8687
use crate::offers::offer::{OfferTlvStream, OfferTlvStreamRef};
8788
use crate::offers::parse::{Bech32Encode, ParseError, ParsedMessage, SemanticError};
8889
use crate::offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef};
90+
use crate::offers::signer::{MetadataMaterial, DerivedPubkey};
8991
use crate::onion_message::BlindedPath;
9092
use crate::util::ser::{SeekReadable, WithoutLength, Writeable, Writer};
9193
use crate::util::string::PrintableString;
@@ -102,6 +104,7 @@ use std::time::SystemTime;
102104
/// [module-level documentation]: self
103105
pub struct RefundBuilder {
104106
refund: RefundContents,
107+
metadata_material: Option<MetadataMaterial>,
105108
}
106109

107110
impl RefundBuilder {
@@ -123,7 +126,53 @@ impl RefundBuilder {
123126
quantity: None, payer_id, payer_note: None,
124127
};
125128

126-
Ok(RefundBuilder { refund })
129+
Ok(RefundBuilder { refund, metadata_material: None })
130+
}
131+
132+
/// Similar to [`RefundBuilder::new`] except it:
133+
/// - derives the payer id such that a different key can be used for each refund, and
134+
/// - sets the metadata when [`RefundBuilder::build`] is called such that it can be used by
135+
/// [`Invoice::verify`] to determine if the refund was produced using a base [`ExpandedKey`]
136+
/// from which the payer id was derived.
137+
///
138+
/// [`Invoice::verify`]: crate::offers::invoice::Invoice::verify
139+
/// [`ExpandedKey`]: crate::ln::inbound_payment::ExpandedKey
140+
#[allow(unused)]
141+
pub(crate) fn deriving_payer_id(
142+
description: String, payer_id: DerivedPubkey, amount_msats: u64
143+
) -> Result<Self, SemanticError> {
144+
if amount_msats > MAX_VALUE_MSAT {
145+
return Err(SemanticError::InvalidAmount);
146+
}
147+
148+
let (payer_id, metadata_material) = payer_id.into_parts();
149+
let refund = RefundContents {
150+
payer: PayerContents(vec![]), description, absolute_expiry: None, issuer: None,
151+
paths: None, chain: None, amount_msats, features: InvoiceRequestFeatures::empty(),
152+
quantity: None, payer_id, payer_note: None,
153+
};
154+
155+
Ok(RefundBuilder { refund, metadata_material: Some(metadata_material) })
156+
}
157+
158+
/// Sets the [`Refund::metadata`] derived from the given `key` and any fields set prior to
159+
/// calling [`Refund::build`]. Allows for stateless verification of an [`Invoice`] when using a
160+
/// public node id as the [`Refund::payer_id`] instead of a derived one.
161+
///
162+
/// Errors if already called or if the builder was constructed with [`Self::deriving_payer_id`].
163+
///
164+
/// [`Invoice`]: crate::offers::invoice::Invoice
165+
#[allow(unused)]
166+
pub(crate) fn metadata_derived(
167+
mut self, key: &ExpandedKey, nonce: Nonce
168+
) -> Result<Self, SemanticError> {
169+
if self.metadata_material.is_some() {
170+
return Err(SemanticError::UnexpectedMetadata);
171+
}
172+
173+
self.refund.payer = PayerContents(vec![]);
174+
self.metadata_material = Some(MetadataMaterial::new(nonce, key));
175+
Ok(self)
127176
}
128177

129178
/// Sets the [`Refund::absolute_expiry`] as seconds since the Unix epoch. Any expiry that has
@@ -190,6 +239,17 @@ impl RefundBuilder {
190239
self.refund.chain = None;
191240
}
192241

242+
// Create the metadata for stateless verification of an Invoice.
243+
if let Some(mut metadata_material) = self.metadata_material {
244+
debug_assert!(self.refund.payer.0.is_empty());
245+
let mut tlv_stream = self.refund.as_tlv_stream();
246+
tlv_stream.0.metadata = None;
247+
tlv_stream.2.payer_id = None;
248+
tlv_stream.write(&mut metadata_material).unwrap();
249+
250+
self.refund.payer.0 = metadata_material.into_metadata();
251+
}
252+
193253
let mut bytes = Vec::new();
194254
self.refund.write(&mut bytes).unwrap();
195255

0 commit comments

Comments
 (0)