Skip to content

Commit 54b4624

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 bd1aad3 commit 54b4624

File tree

5 files changed

+234
-48
lines changed

5 files changed

+234
-48
lines changed

lightning/src/offers/invoice_request.rs

Lines changed: 174 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,114 @@ 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+
if !metadata.derives_keys() {
252+
tlv_stream.2.payer_id = self.payer_id.as_ref();
253+
}
254+
255+
let (derived_metadata, derived_keys) = metadata.derive_from(tlv_stream, self.secp_ctx);
256+
metadata = derived_metadata;
257+
keys = derived_keys;
258+
if let Some(keys) = keys {
259+
debug_assert!(self.payer_id.is_none());
260+
self.payer_id = Some(keys.public_key());
261+
}
262+
263+
self.invoice_request.payer.0 = metadata;
264+
}
265+
266+
debug_assert!(self.invoice_request.payer.0.as_bytes().is_some());
267+
debug_assert!(self.payer_id.is_some());
268+
let payer_id = self.payer_id.unwrap();
269+
270+
let unsigned_invoice = UnsignedInvoiceRequest {
271+
offer: self.offer,
272+
invoice_request: InvoiceRequestContents {
273+
inner: self.invoice_request,
274+
payer_id,
275+
},
276+
};
277+
278+
(unsigned_invoice, keys, secp_ctx)
279+
}
280+
}
281+
282+
impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, ExplicitPayerId, T> {
283+
/// Builds an unsigned [`InvoiceRequest`] after checking for valid semantics. It can be signed
284+
/// by [`UnsignedInvoiceRequest::sign`].
285+
pub fn build(self) -> Result<UnsignedInvoiceRequest<'a>, SemanticError> {
286+
let (unsigned_invoice_request, keys, _) = self.build_with_checks()?;
287+
debug_assert!(keys.is_none());
288+
Ok(unsigned_invoice_request)
289+
}
290+
}
291+
292+
impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, DerivedPayerId, T> {
293+
/// Builds a signed [`InvoiceRequest`] after checking for valid semantics.
294+
pub fn build_and_sign(self) -> Result<InvoiceRequest, SemanticError> {
295+
let (unsigned_invoice_request, keys, secp_ctx) = self.build_with_checks()?;
296+
debug_assert!(keys.is_some());
297+
298+
let secp_ctx = secp_ctx.unwrap();
299+
let keys = keys.unwrap();
300+
let invoice_request = unsigned_invoice_request
301+
.sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys)))
302+
.unwrap();
303+
Ok(invoice_request)
179304
}
180305
}
181306

182307
#[cfg(test)]
183-
impl<'a> InvoiceRequestBuilder<'a> {
308+
impl<'a, 'b, P: PayerIdStrategy, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, P, T> {
184309
fn chain_unchecked(mut self, network: Network) -> Self {
185310
let chain = ChainHash::using_genesis_block(network);
186-
self.invoice_request.inner.chain = Some(chain);
311+
self.invoice_request.chain = Some(chain);
187312
self
188313
}
189314

190315
fn amount_msats_unchecked(mut self, amount_msats: u64) -> Self {
191-
self.invoice_request.inner.amount_msats = Some(amount_msats);
316+
self.invoice_request.amount_msats = Some(amount_msats);
192317
self
193318
}
194319

195320
fn features_unchecked(mut self, features: InvoiceRequestFeatures) -> Self {
196-
self.invoice_request.inner.features = features;
321+
self.invoice_request.features = features;
197322
self
198323
}
199324

200325
fn quantity_unchecked(mut self, quantity: u64) -> Self {
201-
self.invoice_request.inner.quantity = Some(quantity);
326+
self.invoice_request.quantity = Some(quantity);
202327
self
203328
}
204329

205330
pub(super) fn build_unchecked(self) -> UnsignedInvoiceRequest<'a> {
206-
let InvoiceRequestBuilder { offer, invoice_request } = self;
207-
UnsignedInvoiceRequest { offer, invoice_request }
331+
self.build_without_checks().0
208332
}
209333
}
210334

@@ -290,7 +414,7 @@ impl InvoiceRequest {
290414
///
291415
/// [`payer_id`]: Self::payer_id
292416
pub fn metadata(&self) -> &[u8] {
293-
&self.contents.inner.payer.0[..]
417+
self.contents.metadata()
294418
}
295419

296420
/// A chain from [`Offer::chains`] that the offer is valid for.
@@ -402,6 +526,10 @@ impl InvoiceRequest {
402526
}
403527

404528
impl InvoiceRequestContents {
529+
pub fn metadata(&self) -> &[u8] {
530+
self.inner.metadata()
531+
}
532+
405533
pub(super) fn chain(&self) -> ChainHash {
406534
self.inner.chain()
407535
}
@@ -414,13 +542,17 @@ impl InvoiceRequestContents {
414542
}
415543

416544
impl InvoiceRequestContentsWithoutPayerId {
545+
pub(super) fn metadata(&self) -> &[u8] {
546+
self.payer.0.as_bytes().map(|bytes| bytes.as_slice()).unwrap_or(&[])
547+
}
548+
417549
pub(super) fn chain(&self) -> ChainHash {
418550
self.chain.unwrap_or_else(|| self.offer.implied_chain())
419551
}
420552

421553
pub(super) fn as_tlv_stream(&self) -> PartialInvoiceRequestTlvStreamRef {
422554
let payer = PayerTlvStreamRef {
423-
metadata: Some(&self.payer.0),
555+
metadata: self.payer.0.as_bytes(),
424556
};
425557

426558
let offer = self.offer.as_tlv_stream();
@@ -530,7 +662,7 @@ impl TryFrom<PartialInvoiceRequestTlvStream> for InvoiceRequestContents {
530662

531663
let payer = match metadata {
532664
None => return Err(SemanticError::MissingPayerMetadata),
533-
Some(metadata) => PayerContents(metadata),
665+
Some(metadata) => PayerContents(Metadata::Bytes(metadata)),
534666
};
535667
let offer = OfferContents::try_from(offer_tlv_stream)?;
536668

@@ -1038,7 +1170,7 @@ mod tests {
10381170
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
10391171
.amount_msats(1000)
10401172
.build().unwrap()
1041-
.request_invoice(vec![42; 32], payer_pubkey()).unwrap()
1173+
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
10421174
.build().unwrap()
10431175
.sign(payer_sign).unwrap();
10441176

0 commit comments

Comments
 (0)