Skip to content

Commit d8a20ed

Browse files
Merge pull request #1927 from jkczyz/2022-12-invoice-rework
Pre-work for BOLT 12 invoices
2 parents b79ff71 + b50fc4e commit d8a20ed

File tree

5 files changed

+83
-29
lines changed

5 files changed

+83
-29
lines changed

lightning/src/ln/features.rs

+51-9
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ use core::marker::PhantomData;
6565
use bitcoin::bech32;
6666
use bitcoin::bech32::{Base32Len, FromBase32, ToBase32, u5, WriteBase32};
6767
use crate::ln::msgs::DecodeError;
68-
use crate::util::ser::{Readable, Writeable, Writer};
68+
use crate::util::ser::{Readable, WithoutLength, Writeable, Writer};
6969

7070
mod sealed {
7171
use crate::prelude::*;
@@ -159,6 +159,15 @@ mod sealed {
159159
]);
160160
define_context!(OfferContext, []);
161161
define_context!(InvoiceRequestContext, []);
162+
define_context!(Bolt12InvoiceContext, [
163+
// Byte 0
164+
,
165+
// Byte 1
166+
,
167+
// Byte 2
168+
BasicMPP,
169+
]);
170+
define_context!(BlindedHopContext, []);
162171
// This isn't a "real" feature context, and is only used in the channel_type field in an
163172
// `OpenChannel` message.
164173
define_context!(ChannelTypeContext, [
@@ -342,7 +351,7 @@ mod sealed {
342351
define_feature!(15, PaymentSecret, [InitContext, NodeContext, InvoiceContext],
343352
"Feature flags for `payment_secret`.", set_payment_secret_optional, set_payment_secret_required,
344353
supports_payment_secret, requires_payment_secret);
345-
define_feature!(17, BasicMPP, [InitContext, NodeContext, InvoiceContext],
354+
define_feature!(17, BasicMPP, [InitContext, NodeContext, InvoiceContext, Bolt12InvoiceContext],
346355
"Feature flags for `basic_mpp`.", set_basic_mpp_optional, set_basic_mpp_required,
347356
supports_basic_mpp, requires_basic_mpp);
348357
define_feature!(19, Wumbo, [InitContext, NodeContext],
@@ -369,7 +378,7 @@ mod sealed {
369378

370379
#[cfg(test)]
371380
define_feature!(123456789, UnknownFeature,
372-
[NodeContext, ChannelContext, InvoiceContext, OfferContext, InvoiceRequestContext],
381+
[NodeContext, ChannelContext, InvoiceContext, OfferContext, InvoiceRequestContext, Bolt12InvoiceContext, BlindedHopContext],
373382
"Feature flags for an unknown feature used in testing.", set_unknown_feature_optional,
374383
set_unknown_feature_required, supports_unknown_test_feature, requires_unknown_test_feature);
375384
}
@@ -432,6 +441,10 @@ pub type InvoiceFeatures = Features<sealed::InvoiceContext>;
432441
pub type OfferFeatures = Features<sealed::OfferContext>;
433442
/// Features used within an `invoice_request`.
434443
pub type InvoiceRequestFeatures = Features<sealed::InvoiceRequestContext>;
444+
/// Features used within an `invoice`.
445+
pub type Bolt12InvoiceFeatures = Features<sealed::Bolt12InvoiceContext>;
446+
/// Features used within BOLT 4 encrypted_data_tlv and BOLT 12 blinded_payinfo
447+
pub type BlindedHopFeatures = Features<sealed::BlindedHopContext>;
435448

436449
/// Features used within the channel_type field in an OpenChannel message.
437450
///
@@ -719,32 +732,47 @@ impl_feature_len_prefixed_write!(InitFeatures);
719732
impl_feature_len_prefixed_write!(ChannelFeatures);
720733
impl_feature_len_prefixed_write!(NodeFeatures);
721734
impl_feature_len_prefixed_write!(InvoiceFeatures);
735+
impl_feature_len_prefixed_write!(BlindedHopFeatures);
722736

723737
// Some features only appear inside of TLVs, so they don't have a length prefix when serialized.
724738
macro_rules! impl_feature_tlv_write {
725739
($features: ident) => {
726740
impl Writeable for $features {
727741
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
728-
self.write_be(w)
742+
WithoutLength(self).write(w)
729743
}
730744
}
731745
impl Readable for $features {
732746
fn read<R: io::Read>(r: &mut R) -> Result<Self, DecodeError> {
733-
let v = io_extras::read_to_end(r)?;
734-
Ok(Self::from_be_bytes(v))
747+
Ok(WithoutLength::<Self>::read(r)?.0)
735748
}
736749
}
737750
}
738751
}
739752

740753
impl_feature_tlv_write!(ChannelTypeFeatures);
741-
impl_feature_tlv_write!(OfferFeatures);
742-
impl_feature_tlv_write!(InvoiceRequestFeatures);
754+
755+
// Some features may appear both in a TLV record and as part of a TLV subtype sequence. The latter
756+
// requires a length but the former does not.
757+
758+
impl<T: sealed::Context> Writeable for WithoutLength<&Features<T>> {
759+
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
760+
self.0.write_be(w)
761+
}
762+
}
763+
764+
impl<T: sealed::Context> Readable for WithoutLength<Features<T>> {
765+
fn read<R: io::Read>(r: &mut R) -> Result<Self, DecodeError> {
766+
let v = io_extras::read_to_end(r)?;
767+
Ok(WithoutLength(Features::<T>::from_be_bytes(v)))
768+
}
769+
}
743770

744771
#[cfg(test)]
745772
mod tests {
746-
use super::{ChannelFeatures, ChannelTypeFeatures, InitFeatures, InvoiceFeatures, NodeFeatures, sealed};
773+
use super::{ChannelFeatures, ChannelTypeFeatures, InitFeatures, InvoiceFeatures, NodeFeatures, OfferFeatures, sealed};
747774
use bitcoin::bech32::{Base32Len, FromBase32, ToBase32, u5};
775+
use crate::util::ser::{Readable, WithoutLength, Writeable};
748776

749777
#[test]
750778
fn sanity_test_unknown_bits() {
@@ -838,6 +866,20 @@ mod tests {
838866
assert!(features.supports_payment_secret());
839867
}
840868

869+
#[test]
870+
fn encodes_features_without_length() {
871+
let features = OfferFeatures::from_le_bytes(vec![1, 2, 3, 4, 5, 42, 100, 101]);
872+
assert_eq!(features.flags.len(), 8);
873+
874+
let mut serialized_features = Vec::new();
875+
WithoutLength(&features).write(&mut serialized_features).unwrap();
876+
assert_eq!(serialized_features.len(), 8);
877+
878+
let deserialized_features =
879+
WithoutLength::<OfferFeatures>::read(&mut &serialized_features[..]).unwrap().0;
880+
assert_eq!(features, deserialized_features);
881+
}
882+
841883
#[test]
842884
fn invoice_features_encoding() {
843885
let features_as_u5s = vec![

lightning/src/offers/invoice_request.rs

+23-16
Original file line numberDiff line numberDiff line change
@@ -223,11 +223,11 @@ impl<'a> UnsignedInvoiceRequest<'a> {
223223
unsigned_tlv_stream.write(&mut bytes).unwrap();
224224

225225
let pubkey = self.invoice_request.payer_id;
226-
let signature = Some(merkle::sign_message(sign, SIGNATURE_TAG, &bytes, pubkey)?);
226+
let signature = merkle::sign_message(sign, SIGNATURE_TAG, &bytes, pubkey)?;
227227

228228
// Append the signature TLV record to the bytes.
229229
let signature_tlv_stream = SignatureTlvStreamRef {
230-
signature: signature.as_ref(),
230+
signature: Some(&signature),
231231
};
232232
signature_tlv_stream.write(&mut bytes).unwrap();
233233

@@ -249,7 +249,7 @@ impl<'a> UnsignedInvoiceRequest<'a> {
249249
pub struct InvoiceRequest {
250250
pub(super) bytes: Vec<u8>,
251251
contents: InvoiceRequestContents,
252-
signature: Option<Signature>,
252+
signature: Signature,
253253
}
254254

255255
/// The contents of an [`InvoiceRequest`], which may be shared with an `Invoice`.
@@ -311,7 +311,7 @@ impl InvoiceRequest {
311311
/// Signature of the invoice request using [`payer_id`].
312312
///
313313
/// [`payer_id`]: Self::payer_id
314-
pub fn signature(&self) -> Option<Signature> {
314+
pub fn signature(&self) -> Signature {
315315
self.signature
316316
}
317317

@@ -320,7 +320,7 @@ impl InvoiceRequest {
320320
let (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream) =
321321
self.contents.as_tlv_stream();
322322
let signature_tlv_stream = SignatureTlvStreamRef {
323-
signature: self.signature.as_ref(),
323+
signature: Some(&self.signature),
324324
};
325325
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, signature_tlv_stream)
326326
}
@@ -371,7 +371,7 @@ impl Writeable for InvoiceRequestContents {
371371
tlv_stream!(InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef, 80..160, {
372372
(80, chain: ChainHash),
373373
(82, amount: (u64, HighZeroBytesDroppedBigSize)),
374-
(84, features: InvoiceRequestFeatures),
374+
(84, features: (InvoiceRequestFeatures, WithoutLength)),
375375
(86, quantity: (u64, HighZeroBytesDroppedBigSize)),
376376
(88, payer_id: PublicKey),
377377
(89, payer_note: (String, WithoutLength)),
@@ -421,9 +421,11 @@ impl TryFrom<Vec<u8>> for InvoiceRequest {
421421
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream)
422422
)?;
423423

424-
if let Some(signature) = &signature {
425-
merkle::verify_signature(signature, SIGNATURE_TAG, &bytes, contents.payer_id)?;
426-
}
424+
let signature = match signature {
425+
None => return Err(ParseError::InvalidSemantics(SemanticError::MissingSignature)),
426+
Some(signature) => signature,
427+
};
428+
merkle::verify_signature(&signature, SIGNATURE_TAG, &bytes, contents.payer_id)?;
427429

428430
Ok(InvoiceRequest { bytes, contents, signature })
429431
}
@@ -471,7 +473,7 @@ impl TryFrom<PartialInvoiceRequestTlvStream> for InvoiceRequestContents {
471473

472474
#[cfg(test)]
473475
mod tests {
474-
use super::{InvoiceRequest, InvoiceRequestTlvStreamRef};
476+
use super::{InvoiceRequest, InvoiceRequestTlvStreamRef, SIGNATURE_TAG};
475477

476478
use bitcoin::blockdata::constants::ChainHash;
477479
use bitcoin::network::constants::Network;
@@ -483,7 +485,7 @@ mod tests {
483485
use core::time::Duration;
484486
use crate::ln::features::InvoiceRequestFeatures;
485487
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
486-
use crate::offers::merkle::{SignError, SignatureTlvStreamRef};
488+
use crate::offers::merkle::{SignError, SignatureTlvStreamRef, self};
487489
use crate::offers::offer::{Amount, OfferBuilder, OfferTlvStreamRef, Quantity};
488490
use crate::offers::parse::{ParseError, SemanticError};
489491
use crate::offers::payer::PayerTlvStreamRef;
@@ -536,7 +538,11 @@ mod tests {
536538
assert_eq!(invoice_request.quantity(), None);
537539
assert_eq!(invoice_request.payer_id(), payer_pubkey());
538540
assert_eq!(invoice_request.payer_note(), None);
539-
assert!(invoice_request.signature().is_some());
541+
assert!(
542+
merkle::verify_signature(
543+
&invoice_request.signature, SIGNATURE_TAG, &invoice_request.bytes, payer_pubkey()
544+
).is_ok()
545+
);
540546

541547
assert_eq!(
542548
invoice_request.as_tlv_stream(),
@@ -563,7 +569,7 @@ mod tests {
563569
payer_id: Some(&payer_pubkey()),
564570
payer_note: None,
565571
},
566-
SignatureTlvStreamRef { signature: invoice_request.signature().as_ref() },
572+
SignatureTlvStreamRef { signature: Some(&invoice_request.signature()) },
567573
),
568574
);
569575

@@ -1222,7 +1228,7 @@ mod tests {
12221228
}
12231229

12241230
#[test]
1225-
fn parses_invoice_request_without_signature() {
1231+
fn fails_parsing_invoice_request_without_signature() {
12261232
let mut buffer = Vec::new();
12271233
OfferBuilder::new("foo".into(), recipient_pubkey())
12281234
.amount_msats(1000)
@@ -1232,8 +1238,9 @@ mod tests {
12321238
.invoice_request
12331239
.write(&mut buffer).unwrap();
12341240

1235-
if let Err(e) = InvoiceRequest::try_from(buffer) {
1236-
panic!("error parsing invoice_request: {:?}", e);
1241+
match InvoiceRequest::try_from(buffer) {
1242+
Ok(_) => panic!("expected error"),
1243+
Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingSignature)),
12371244
}
12381245
}
12391246

lightning/src/offers/offer.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -567,7 +567,7 @@ tlv_stream!(OfferTlvStream, OfferTlvStreamRef, 1..80, {
567567
(6, currency: CurrencyCode),
568568
(8, amount: (u64, HighZeroBytesDroppedBigSize)),
569569
(10, description: (String, WithoutLength)),
570-
(12, features: OfferFeatures),
570+
(12, features: (OfferFeatures, WithoutLength)),
571571
(14, absolute_expiry: (u64, HighZeroBytesDroppedBigSize)),
572572
(16, paths: (Vec<BlindedPath>, WithoutLength)),
573573
(18, issuer: (String, WithoutLength)),

lightning/src/offers/parse.rs

+2
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,8 @@ pub enum SemanticError {
159159
MissingPayerMetadata,
160160
/// A payer id was expected but was missing.
161161
MissingPayerId,
162+
/// A signature was expected but was missing.
163+
MissingSignature,
162164
}
163165

164166
impl From<bech32::Error> for ParseError {

lightning/src/offers/refund.rs

+6-3
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,8 @@ pub struct RefundBuilder {
102102
}
103103

104104
impl RefundBuilder {
105-
/// Creates a new builder for a refund using the [`Refund::payer_id`] for signing invoices. Use
106-
/// a different pubkey per refund to avoid correlating refunds.
105+
/// Creates a new builder for a refund using the [`Refund::payer_id`] for the public node id to
106+
/// send to if no [`Refund::paths`] are set. Otherwise, it may be a transient pubkey.
107107
///
108108
/// Additionally, sets the required [`Refund::description`], [`Refund::metadata`], and
109109
/// [`Refund::amount_msats`].
@@ -285,7 +285,10 @@ impl Refund {
285285
&self.contents.features
286286
}
287287

288-
/// A possibly transient pubkey used to sign the refund.
288+
/// A public node id to send to in the case where there are no [`paths`]. Otherwise, a possibly
289+
/// transient pubkey.
290+
///
291+
/// [`paths`]: Self::paths
289292
pub fn payer_id(&self) -> PublicKey {
290293
self.contents.payer_id
291294
}

0 commit comments

Comments
 (0)