Skip to content

Commit 92c9763

Browse files
committed
InvoiceRequest metadata and payer id derivation
Add support for deriving a transient payer id for each InvoiceRequest from an ExpandedKey and a nonce. This facilitates payer privacy by not tying any InvoiceRequest 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 19e0f07 commit 92c9763

File tree

5 files changed

+231
-48
lines changed

5 files changed

+231
-48
lines changed

lightning/src/offers/invoice_request.rs

Lines changed: 171 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -54,19 +54,22 @@
5454
5555
use bitcoin::blockdata::constants::ChainHash;
5656
use bitcoin::network::constants::Network;
57-
use bitcoin::secp256k1::{Message, PublicKey, Secp256k1, self};
57+
use bitcoin::secp256k1::{KeyPair, Message, PublicKey, Secp256k1, self};
5858
use bitcoin::secp256k1::schnorr::Signature;
59-
use core::convert::TryFrom;
59+
use core::convert::{Infallible, TryFrom};
60+
use core::ops::Deref;
61+
use crate::chain::keysinterface::EntropySource;
6062
use crate::io;
6163
use crate::ln::PaymentHash;
6264
use crate::ln::features::InvoiceRequestFeatures;
63-
use crate::ln::inbound_payment::ExpandedKey;
65+
use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce};
6466
use crate::ln::msgs::DecodeError;
6567
use crate::offers::invoice::{BlindedPayInfo, InvoiceBuilder};
6668
use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, TlvStream, self};
6769
use crate::offers::offer::{Offer, OfferContents, OfferTlvStream, OfferTlvStreamRef};
6870
use crate::offers::parse::{ParseError, ParsedMessage, SemanticError};
6971
use crate::offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef};
72+
use crate::offers::signer::{Metadata, MetadataMaterial};
7073
use crate::onion_message::BlindedPath;
7174
use crate::util::ser::{HighZeroBytesDroppedBigSize, SeekReadable, WithoutLength, Writeable, Writer};
7275
use crate::util::string::PrintableString;
@@ -75,28 +78,83 @@ use crate::prelude::*;
7578

7679
const SIGNATURE_TAG: &'static str = concat!("lightning", "invoice_request", "signature");
7780

81+
const IV_BYTES: &[u8; IV_LEN] = b"LDK Invreq ~~~~~";
82+
7883
/// Builds an [`InvoiceRequest`] from an [`Offer`] for the "offer to be paid" flow.
7984
///
8085
/// See [module-level documentation] for usage.
8186
///
8287
/// [module-level documentation]: self
83-
pub struct InvoiceRequestBuilder<'a> {
88+
pub struct InvoiceRequestBuilder<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signing> {
8489
offer: &'a Offer,
85-
invoice_request: InvoiceRequestContents,
90+
invoice_request: InvoiceRequestContentsWithoutPayerId,
91+
payer_id: Option<PublicKey>,
92+
payer_id_strategy: core::marker::PhantomData<P>,
93+
secp_ctx: Option<&'b Secp256k1<T>>,
8694
}
8795

88-
impl<'a> InvoiceRequestBuilder<'a> {
96+
/// Indicates how [`InvoiceRequest::payer_id`] will be set.
97+
pub trait PayerIdStrategy {}
98+
99+
/// [`InvoiceRequest::payer_id`] will be explicitly set.
100+
pub struct ExplicitPayerId {}
101+
102+
/// [`InvoiceRequest::payer_id`] will be derived.
103+
pub struct DerivedPayerId {}
104+
105+
impl PayerIdStrategy for ExplicitPayerId {}
106+
impl PayerIdStrategy for DerivedPayerId {}
107+
108+
impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, ExplicitPayerId, T> {
89109
pub(super) fn new(offer: &'a Offer, metadata: Vec<u8>, payer_id: PublicKey) -> Self {
90110
Self {
91111
offer,
92-
invoice_request: InvoiceRequestContents {
93-
inner: InvoiceRequestContentsWithoutPayerId {
94-
payer: PayerContents(metadata), offer: offer.contents.clone(), chain: None,
95-
amount_msats: None, features: InvoiceRequestFeatures::empty(), quantity: None,
96-
payer_note: None,
97-
},
98-
payer_id,
99-
},
112+
invoice_request: Self::create_contents(offer, Metadata::Bytes(metadata)),
113+
payer_id: Some(payer_id),
114+
payer_id_strategy: core::marker::PhantomData,
115+
secp_ctx: None,
116+
}
117+
}
118+
119+
pub(super) fn deriving_metadata<ES: Deref>(
120+
offer: &'a Offer, payer_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES
121+
) -> Self where ES::Target: EntropySource {
122+
let nonce = Nonce::from_entropy_source(entropy_source);
123+
let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES);
124+
let metadata = Metadata::Derived(derivation_material);
125+
Self {
126+
offer,
127+
invoice_request: Self::create_contents(offer, metadata),
128+
payer_id: Some(payer_id),
129+
payer_id_strategy: core::marker::PhantomData,
130+
secp_ctx: None,
131+
}
132+
}
133+
}
134+
135+
impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, DerivedPayerId, T> {
136+
pub(super) fn deriving_payer_id<ES: Deref>(
137+
offer: &'a Offer, expanded_key: &ExpandedKey, entropy_source: ES, secp_ctx: &'b Secp256k1<T>
138+
) -> Self where ES::Target: EntropySource {
139+
let nonce = Nonce::from_entropy_source(entropy_source);
140+
let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES);
141+
let metadata = Metadata::DerivedSigningPubkey(derivation_material);
142+
Self {
143+
offer,
144+
invoice_request: Self::create_contents(offer, metadata),
145+
payer_id: None,
146+
payer_id_strategy: core::marker::PhantomData,
147+
secp_ctx: Some(secp_ctx),
148+
}
149+
}
150+
}
151+
152+
impl<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, P, T> {
153+
fn create_contents(offer: &Offer, metadata: Metadata) -> InvoiceRequestContentsWithoutPayerId {
154+
let offer = offer.contents.clone();
155+
InvoiceRequestContentsWithoutPayerId {
156+
payer: PayerContents(metadata), offer, chain: None, amount_msats: None,
157+
features: InvoiceRequestFeatures::empty(), quantity: None, payer_note: None,
100158
}
101159
}
102160

@@ -111,7 +169,7 @@ impl<'a> InvoiceRequestBuilder<'a> {
111169
return Err(SemanticError::UnsupportedChain);
112170
}
113171

114-
self.invoice_request.inner.chain = Some(chain);
172+
self.invoice_request.chain = Some(chain);
115173
Ok(self)
116174
}
117175

@@ -122,10 +180,10 @@ impl<'a> InvoiceRequestBuilder<'a> {
122180
///
123181
/// [`quantity`]: Self::quantity
124182
pub fn amount_msats(mut self, amount_msats: u64) -> Result<Self, SemanticError> {
125-
self.invoice_request.inner.offer.check_amount_msats_for_quantity(
126-
Some(amount_msats), self.invoice_request.inner.quantity
183+
self.invoice_request.offer.check_amount_msats_for_quantity(
184+
Some(amount_msats), self.invoice_request.quantity
127185
)?;
128-
self.invoice_request.inner.amount_msats = Some(amount_msats);
186+
self.invoice_request.amount_msats = Some(amount_msats);
129187
Ok(self)
130188
}
131189

@@ -134,22 +192,23 @@ impl<'a> InvoiceRequestBuilder<'a> {
134192
///
135193
/// Successive calls to this method will override the previous setting.
136194
pub fn quantity(mut self, quantity: u64) -> Result<Self, SemanticError> {
137-
self.invoice_request.inner.offer.check_quantity(Some(quantity))?;
138-
self.invoice_request.inner.quantity = Some(quantity);
195+
self.invoice_request.offer.check_quantity(Some(quantity))?;
196+
self.invoice_request.quantity = Some(quantity);
139197
Ok(self)
140198
}
141199

142200
/// Sets the [`InvoiceRequest::payer_note`].
143201
///
144202
/// Successive calls to this method will override the previous setting.
145203
pub fn payer_note(mut self, payer_note: String) -> Self {
146-
self.invoice_request.inner.payer_note = Some(payer_note);
204+
self.invoice_request.payer_note = Some(payer_note);
147205
self
148206
}
149207

150-
/// Builds an unsigned [`InvoiceRequest`] after checking for valid semantics. It can be signed
151-
/// by [`UnsignedInvoiceRequest::sign`].
152-
pub fn build(mut self) -> Result<UnsignedInvoiceRequest<'a>, SemanticError> {
208+
fn build_with_checks(mut self) -> Result<
209+
(UnsignedInvoiceRequest<'a>, Option<KeyPair>, Option<&'b Secp256k1<T>>),
210+
SemanticError
211+
> {
153212
#[cfg(feature = "std")] {
154213
if self.offer.is_expired() {
155214
return Err(SemanticError::AlreadyExpired);
@@ -162,49 +221,111 @@ impl<'a> InvoiceRequestBuilder<'a> {
162221
}
163222

164223
if chain == self.offer.implied_chain() {
165-
self.invoice_request.inner.chain = None;
224+
self.invoice_request.chain = None;
166225
}
167226

168-
if self.offer.amount().is_none() && self.invoice_request.inner.amount_msats.is_none() {
227+
if self.offer.amount().is_none() && self.invoice_request.amount_msats.is_none() {
169228
return Err(SemanticError::MissingAmount);
170229
}
171230

172-
self.invoice_request.inner.offer.check_quantity(self.invoice_request.inner.quantity)?;
173-
self.invoice_request.inner.offer.check_amount_msats_for_quantity(
174-
self.invoice_request.inner.amount_msats, self.invoice_request.inner.quantity
231+
self.invoice_request.offer.check_quantity(self.invoice_request.quantity)?;
232+
self.invoice_request.offer.check_amount_msats_for_quantity(
233+
self.invoice_request.amount_msats, self.invoice_request.quantity
175234
)?;
176235

177-
let InvoiceRequestBuilder { offer, invoice_request } = self;
178-
Ok(UnsignedInvoiceRequest { offer, invoice_request })
236+
Ok(self.build_without_checks())
237+
}
238+
239+
fn build_without_checks(mut self) ->
240+
(UnsignedInvoiceRequest<'a>, Option<KeyPair>, Option<&'b Secp256k1<T>>)
241+
{
242+
// Create the metadata for stateless verification of an Invoice.
243+
let mut keys = None;
244+
let secp_ctx = self.secp_ctx.clone();
245+
if self.invoice_request.payer.0.has_derivation_material() {
246+
let mut metadata = core::mem::take(&mut self.invoice_request.payer.0);
247+
248+
let mut tlv_stream = self.invoice_request.as_tlv_stream();
249+
debug_assert!(tlv_stream.2.payer_id.is_none());
250+
tlv_stream.0.metadata = None;
251+
252+
let (derived_metadata, derived_keys) = metadata.derive_from(tlv_stream, self.secp_ctx);
253+
metadata = derived_metadata;
254+
keys = derived_keys;
255+
if let Some(keys) = keys {
256+
debug_assert!(self.payer_id.is_none());
257+
self.payer_id = Some(keys.public_key());
258+
}
259+
260+
self.invoice_request.payer.0 = metadata;
261+
}
262+
263+
debug_assert!(self.invoice_request.payer.0.as_bytes().is_some());
264+
debug_assert!(self.payer_id.is_some());
265+
let payer_id = self.payer_id.unwrap();
266+
267+
let unsigned_invoice = UnsignedInvoiceRequest {
268+
offer: self.offer,
269+
invoice_request: InvoiceRequestContents {
270+
inner: self.invoice_request,
271+
payer_id,
272+
},
273+
};
274+
275+
(unsigned_invoice, keys, secp_ctx)
276+
}
277+
}
278+
279+
impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, ExplicitPayerId, T> {
280+
/// Builds an unsigned [`InvoiceRequest`] after checking for valid semantics. It can be signed
281+
/// by [`UnsignedInvoiceRequest::sign`].
282+
pub fn build(self) -> Result<UnsignedInvoiceRequest<'a>, SemanticError> {
283+
let (unsigned_invoice_request, keys, _) = self.build_with_checks()?;
284+
debug_assert!(keys.is_none());
285+
Ok(unsigned_invoice_request)
286+
}
287+
}
288+
289+
impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, DerivedPayerId, T> {
290+
/// Builds a signed [`InvoiceRequest`] after checking for valid semantics.
291+
pub fn build_and_sign(self) -> Result<InvoiceRequest, SemanticError> {
292+
let (unsigned_invoice_request, keys, secp_ctx) = self.build_with_checks()?;
293+
debug_assert!(keys.is_some());
294+
295+
let secp_ctx = secp_ctx.unwrap();
296+
let keys = keys.unwrap();
297+
let invoice_request = unsigned_invoice_request
298+
.sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys)))
299+
.unwrap();
300+
Ok(invoice_request)
179301
}
180302
}
181303

182304
#[cfg(test)]
183-
impl<'a> InvoiceRequestBuilder<'a> {
305+
impl<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, P, T> {
184306
fn chain_unchecked(mut self, network: Network) -> Self {
185307
let chain = ChainHash::using_genesis_block(network);
186-
self.invoice_request.inner.chain = Some(chain);
308+
self.invoice_request.chain = Some(chain);
187309
self
188310
}
189311

190312
fn amount_msats_unchecked(mut self, amount_msats: u64) -> Self {
191-
self.invoice_request.inner.amount_msats = Some(amount_msats);
313+
self.invoice_request.amount_msats = Some(amount_msats);
192314
self
193315
}
194316

195317
fn features_unchecked(mut self, features: InvoiceRequestFeatures) -> Self {
196-
self.invoice_request.inner.features = features;
318+
self.invoice_request.features = features;
197319
self
198320
}
199321

200322
fn quantity_unchecked(mut self, quantity: u64) -> Self {
201-
self.invoice_request.inner.quantity = Some(quantity);
323+
self.invoice_request.quantity = Some(quantity);
202324
self
203325
}
204326

205327
pub(super) fn build_unchecked(self) -> UnsignedInvoiceRequest<'a> {
206-
let InvoiceRequestBuilder { offer, invoice_request } = self;
207-
UnsignedInvoiceRequest { offer, invoice_request }
328+
self.build_without_checks().0
208329
}
209330
}
210331

@@ -290,7 +411,7 @@ impl InvoiceRequest {
290411
///
291412
/// [`payer_id`]: Self::payer_id
292413
pub fn metadata(&self) -> &[u8] {
293-
&self.contents.inner.payer.0[..]
414+
self.contents.metadata()
294415
}
295416

296417
/// A chain from [`Offer::chains`] that the offer is valid for.
@@ -402,6 +523,10 @@ impl InvoiceRequest {
402523
}
403524

404525
impl InvoiceRequestContents {
526+
pub fn metadata(&self) -> &[u8] {
527+
self.inner.metadata()
528+
}
529+
405530
pub(super) fn chain(&self) -> ChainHash {
406531
self.inner.chain()
407532
}
@@ -414,13 +539,17 @@ impl InvoiceRequestContents {
414539
}
415540

416541
impl InvoiceRequestContentsWithoutPayerId {
542+
pub(super) fn metadata(&self) -> &[u8] {
543+
self.payer.0.as_bytes().map(|bytes| bytes.as_slice()).unwrap_or(&[])
544+
}
545+
417546
pub(super) fn chain(&self) -> ChainHash {
418547
self.chain.unwrap_or_else(|| self.offer.implied_chain())
419548
}
420549

421550
pub(super) fn as_tlv_stream(&self) -> PartialInvoiceRequestTlvStreamRef {
422551
let payer = PayerTlvStreamRef {
423-
metadata: Some(&self.payer.0),
552+
metadata: self.payer.0.as_bytes(),
424553
};
425554

426555
let offer = self.offer.as_tlv_stream();
@@ -530,7 +659,7 @@ impl TryFrom<PartialInvoiceRequestTlvStream> for InvoiceRequestContents {
530659

531660
let payer = match metadata {
532661
None => return Err(SemanticError::MissingPayerMetadata),
533-
Some(metadata) => PayerContents(metadata),
662+
Some(metadata) => PayerContents(Metadata::Bytes(metadata)),
534663
};
535664
let offer = OfferContents::try_from(offer_tlv_stream)?;
536665

@@ -1038,7 +1167,7 @@ mod tests {
10381167
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
10391168
.amount_msats(1000)
10401169
.build().unwrap()
1041-
.request_invoice(vec![42; 32], payer_pubkey()).unwrap()
1170+
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
10421171
.build().unwrap()
10431172
.sign(payer_sign).unwrap();
10441173

0 commit comments

Comments
 (0)