Skip to content

Commit 771aa8e

Browse files
committed
Separate bytes for experimental TLVs
When constructing UnsignedInvoiceRequest or UnsignedBolt12Invoice, use a separate field for experimental TLV bytes. This allows for properly inserting the signature TLVs before the experimental TLVs when signing.
1 parent e23f89c commit 771aa8e

File tree

5 files changed

+264
-71
lines changed

5 files changed

+264
-71
lines changed

lightning/src/offers/invoice.rs

Lines changed: 91 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -121,10 +121,10 @@ use crate::ln::msgs::DecodeError;
121121
use crate::offers::invoice_macros::{invoice_accessors_common, invoice_builder_methods_common};
122122
#[cfg(test)]
123123
use crate::offers::invoice_macros::invoice_builder_methods_test;
124-
use crate::offers::invoice_request::{INVOICE_REQUEST_PAYER_ID_TYPE, INVOICE_REQUEST_TYPES, IV_BYTES as INVOICE_REQUEST_IV_BYTES, InvoiceRequest, InvoiceRequestContents, InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef};
125-
use crate::offers::merkle::{SignError, SignFn, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, TlvStream, WithoutSignatures, self};
124+
use crate::offers::invoice_request::{EXPERIMENTAL_INVOICE_REQUEST_TYPES, INVOICE_REQUEST_PAYER_ID_TYPE, INVOICE_REQUEST_TYPES, IV_BYTES as INVOICE_REQUEST_IV_BYTES, InvoiceRequest, InvoiceRequestContents, InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef};
125+
use crate::offers::merkle::{SignError, SignFn, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, TlvStream, self, SIGNATURE_TLV_RECORD_SIZE};
126126
use crate::offers::nonce::Nonce;
127-
use crate::offers::offer::{Amount, OFFER_TYPES, OfferTlvStream, OfferTlvStreamRef, Quantity};
127+
use crate::offers::offer::{Amount, EXPERIMENTAL_OFFER_TYPES, OFFER_TYPES, OfferTlvStream, OfferTlvStreamRef, Quantity};
128128
use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError, ParsedMessage};
129129
use crate::offers::payer::{PAYER_METADATA_TYPE, PayerTlvStream, PayerTlvStreamRef};
130130
use crate::offers::refund::{IV_BYTES_WITH_METADATA as REFUND_IV_BYTES_WITH_METADATA, IV_BYTES_WITHOUT_METADATA as REFUND_IV_BYTES_WITHOUT_METADATA, Refund, RefundContents};
@@ -461,6 +461,7 @@ for InvoiceBuilder<'a, DerivedSigningPubkey> {
461461
#[derive(Clone)]
462462
pub struct UnsignedBolt12Invoice {
463463
bytes: Vec<u8>,
464+
experimental_bytes: Vec<u8>,
464465
contents: InvoiceContents,
465466
tagged_hash: TaggedHash,
466467
}
@@ -491,19 +492,57 @@ where
491492

492493
impl UnsignedBolt12Invoice {
493494
fn new(invreq_bytes: &[u8], contents: InvoiceContents) -> Self {
495+
// TLV record ranges applicable to invreq_bytes.
496+
const NON_EXPERIMENTAL_TYPES: core::ops::Range<u64> = 0..INVOICE_REQUEST_TYPES.end;
497+
const EXPERIMENTAL_TYPES: core::ops::Range<u64> =
498+
EXPERIMENTAL_OFFER_TYPES.start..EXPERIMENTAL_INVOICE_REQUEST_TYPES.end;
499+
500+
let (_, _, _, invoice_tlv_stream) = contents.as_tlv_stream();
501+
502+
// Allocate enough space for the invoice, which will include:
503+
// - all TLV records from `invreq_bytes` except signatures,
504+
// - all invoice-specific TLV records, and
505+
// - a signature TLV record once the invoice is signed.
506+
//
507+
// This assumes both the invoice request and the invoice will each only have one signature
508+
// using SIGNATURE_TYPES.start as the TLV record. Thus, it is accounted for by invreq_bytes.
509+
let mut bytes = Vec::with_capacity(
510+
invreq_bytes.len()
511+
+ invoice_tlv_stream.serialized_length()
512+
+ if contents.is_for_offer() { 0 } else { SIGNATURE_TLV_RECORD_SIZE }
513+
);
514+
494515
// Use the invoice_request bytes instead of the invoice_request TLV stream as the latter may
495516
// have contained unknown TLV records, which are not stored in `InvoiceRequestContents` or
496517
// `RefundContents`.
497-
let (_, _, _, invoice_tlv_stream) = contents.as_tlv_stream();
498-
let invoice_request_bytes = WithoutSignatures(invreq_bytes);
499-
let unsigned_tlv_stream = (invoice_request_bytes, invoice_tlv_stream);
518+
for record in TlvStream::new(invreq_bytes).range(NON_EXPERIMENTAL_TYPES) {
519+
record.write(&mut bytes).unwrap();
520+
}
500521

501-
let mut bytes = Vec::new();
502-
unsigned_tlv_stream.write(&mut bytes).unwrap();
522+
let remaining_bytes = &invreq_bytes[bytes.len()..];
503523

504-
let tagged_hash = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &bytes);
524+
invoice_tlv_stream.write(&mut bytes).unwrap();
525+
526+
let mut experimental_tlv_stream = TlvStream::new(remaining_bytes)
527+
.range(EXPERIMENTAL_TYPES)
528+
.peekable();
529+
let mut experimental_bytes = Vec::with_capacity(
530+
remaining_bytes.len()
531+
- experimental_tlv_stream
532+
.peek()
533+
.map_or(remaining_bytes.len(), |first_record| first_record.start)
534+
);
535+
536+
for record in experimental_tlv_stream {
537+
record.write(&mut experimental_bytes).unwrap();
538+
}
505539

506-
Self { bytes, contents, tagged_hash }
540+
debug_assert_eq!(experimental_bytes.len(), experimental_bytes.capacity());
541+
542+
let tlv_stream = TlvStream::new(&bytes).chain(TlvStream::new(&experimental_bytes));
543+
let tagged_hash = TaggedHash::from_tlv_stream(SIGNATURE_TAG, tlv_stream);
544+
545+
Self { bytes, experimental_bytes, contents, tagged_hash }
507546
}
508547

509548
/// Returns the [`TaggedHash`] of the invoice to sign.
@@ -528,6 +567,17 @@ macro_rules! unsigned_invoice_sign_method { ($self: ident, $self_type: ty $(, $s
528567
};
529568
signature_tlv_stream.write(&mut $self.bytes).unwrap();
530569

570+
// Append the experimental bytes after the signature.
571+
debug_assert_eq!(
572+
// The two-byte overallocation results from SIGNATURE_TLV_RECORD_SIZE accommodating TLV
573+
// records with types >= 253.
574+
$self.bytes.len()
575+
+ $self.experimental_bytes.len()
576+
+ if $self.contents.is_for_offer() { 0 } else { 2 },
577+
$self.bytes.capacity(),
578+
);
579+
$self.bytes.extend_from_slice(&$self.experimental_bytes);
580+
531581
Ok(Bolt12Invoice {
532582
#[cfg(not(c_bindings))]
533583
bytes: $self.bytes,
@@ -882,6 +932,13 @@ impl Hash for Bolt12Invoice {
882932
}
883933

884934
impl InvoiceContents {
935+
fn is_for_offer(&self) -> bool {
936+
match self {
937+
InvoiceContents::ForOffer { .. } => true,
938+
InvoiceContents::ForRefund { .. } => false,
939+
}
940+
}
941+
885942
/// Whether the original offer or refund has expired.
886943
#[cfg(feature = "std")]
887944
fn is_offer_or_refund_expired(&self) -> bool {
@@ -1211,7 +1268,7 @@ impl TryFrom<Vec<u8>> for UnsignedBolt12Invoice {
12111268

12121269
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
12131270
let invoice = ParsedMessage::<PartialInvoiceTlvStream>::try_from(bytes)?;
1214-
let ParsedMessage { bytes, tlv_stream } = invoice;
1271+
let ParsedMessage { mut bytes, tlv_stream } = invoice;
12151272
let (
12161273
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
12171274
) = tlv_stream;
@@ -1221,7 +1278,13 @@ impl TryFrom<Vec<u8>> for UnsignedBolt12Invoice {
12211278

12221279
let tagged_hash = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &bytes);
12231280

1224-
Ok(UnsignedBolt12Invoice { bytes, contents, tagged_hash })
1281+
let offset = TlvStream::new(&bytes)
1282+
.range(0..INVOICE_TYPES.end)
1283+
.last()
1284+
.map_or(0, |last_record| last_record.end);
1285+
let experimental_bytes = bytes.split_off(offset);
1286+
1287+
Ok(UnsignedBolt12Invoice { bytes, experimental_bytes, contents, tagged_hash })
12251288
}
12261289
}
12271290

@@ -2512,10 +2575,15 @@ mod tests {
25122575
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
25132576
.build().unwrap();
25142577

2515-
BigSize(UNKNOWN_ODD_TYPE).write(&mut unsigned_invoice.bytes).unwrap();
2516-
BigSize(32).write(&mut unsigned_invoice.bytes).unwrap();
2517-
[42u8; 32].write(&mut unsigned_invoice.bytes).unwrap();
2578+
let mut unknown_bytes = Vec::new();
2579+
BigSize(UNKNOWN_ODD_TYPE).write(&mut unknown_bytes).unwrap();
2580+
BigSize(32).write(&mut unknown_bytes).unwrap();
2581+
[42u8; 32].write(&mut unknown_bytes).unwrap();
25182582

2583+
unsigned_invoice.bytes.reserve_exact(
2584+
unsigned_invoice.bytes.capacity() - unsigned_invoice.bytes.len() + unknown_bytes.len(),
2585+
);
2586+
unsigned_invoice.bytes.extend_from_slice(&unknown_bytes);
25192587
unsigned_invoice.tagged_hash =
25202588
TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &unsigned_invoice.bytes);
25212589

@@ -2545,10 +2613,15 @@ mod tests {
25452613
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
25462614
.build().unwrap();
25472615

2548-
BigSize(UNKNOWN_EVEN_TYPE).write(&mut unsigned_invoice.bytes).unwrap();
2549-
BigSize(32).write(&mut unsigned_invoice.bytes).unwrap();
2550-
[42u8; 32].write(&mut unsigned_invoice.bytes).unwrap();
2616+
let mut unknown_bytes = Vec::new();
2617+
BigSize(UNKNOWN_EVEN_TYPE).write(&mut unknown_bytes).unwrap();
2618+
BigSize(32).write(&mut unknown_bytes).unwrap();
2619+
[42u8; 32].write(&mut unknown_bytes).unwrap();
25512620

2621+
unsigned_invoice.bytes.reserve_exact(
2622+
unsigned_invoice.bytes.capacity() - unsigned_invoice.bytes.len() + unknown_bytes.len(),
2623+
);
2624+
unsigned_invoice.bytes.extend_from_slice(&unknown_bytes);
25522625
unsigned_invoice.tagged_hash =
25532626
TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &unsigned_invoice.bytes);
25542627

lightning/src/offers/invoice_request.rs

Lines changed: 86 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,9 @@ use crate::ln::channelmanager::PaymentId;
6969
use crate::ln::features::InvoiceRequestFeatures;
7070
use crate::ln::inbound_payment::{ExpandedKey, IV_LEN};
7171
use crate::ln::msgs::DecodeError;
72-
use crate::offers::merkle::{SignError, SignFn, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, self};
72+
use crate::offers::merkle::{SignError, SignFn, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, TlvStream, self, SIGNATURE_TLV_RECORD_SIZE};
7373
use crate::offers::nonce::Nonce;
74-
use crate::offers::offer::{Offer, OfferContents, OfferId, OfferTlvStream, OfferTlvStreamRef};
74+
use crate::offers::offer::{EXPERIMENTAL_OFFER_TYPES, OFFER_TYPES, Offer, OfferContents, OfferId, OfferTlvStream, OfferTlvStreamRef};
7575
use crate::offers::parse::{Bolt12ParseError, ParsedMessage, Bolt12SemanticError};
7676
use crate::offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef};
7777
use crate::offers::signer::{Metadata, MetadataMaterial};
@@ -488,6 +488,7 @@ for InvoiceRequestBuilder<'a, 'b, DerivedPayerSigningPubkey, secp256k1::All> {
488488
#[derive(Clone)]
489489
pub struct UnsignedInvoiceRequest {
490490
bytes: Vec<u8>,
491+
experimental_bytes: Vec<u8>,
491492
contents: InvoiceRequestContents,
492493
tagged_hash: TaggedHash,
493494
}
@@ -520,17 +521,51 @@ impl UnsignedInvoiceRequest {
520521
fn new(offer: &Offer, contents: InvoiceRequestContents) -> Self {
521522
// Use the offer bytes instead of the offer TLV stream as the offer may have contained
522523
// unknown TLV records, which are not stored in `OfferContents`.
523-
let (payer_tlv_stream, _offer_tlv_stream, invoice_request_tlv_stream) =
524-
contents.as_tlv_stream();
525-
let offer_bytes = WithoutLength(&offer.bytes);
526-
let unsigned_tlv_stream = (payer_tlv_stream, offer_bytes, invoice_request_tlv_stream);
524+
let (
525+
payer_tlv_stream, _offer_tlv_stream, invoice_request_tlv_stream,
526+
) = contents.as_tlv_stream();
527+
528+
// Allocate enough space for the invoice_request, which will include:
529+
// - all TLV records from `offer.bytes`,
530+
// - all invoice_request-specific TLV records, and
531+
// - a signature TLV record once the invoice_request is signed.
532+
let mut bytes = Vec::with_capacity(
533+
offer.bytes.len()
534+
+ payer_tlv_stream.serialized_length()
535+
+ invoice_request_tlv_stream.serialized_length()
536+
+ SIGNATURE_TLV_RECORD_SIZE
537+
);
527538

528-
let mut bytes = Vec::new();
529-
unsigned_tlv_stream.write(&mut bytes).unwrap();
539+
payer_tlv_stream.write(&mut bytes).unwrap();
530540

531-
let tagged_hash = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &bytes);
541+
for record in TlvStream::new(&offer.bytes).range(OFFER_TYPES) {
542+
record.write(&mut bytes).unwrap();
543+
}
544+
545+
let remaining_bytes = &offer.bytes[bytes.len() - payer_tlv_stream.serialized_length()..];
546+
547+
invoice_request_tlv_stream.write(&mut bytes).unwrap();
548+
549+
let mut experimental_tlv_stream = TlvStream::new(remaining_bytes)
550+
.range(EXPERIMENTAL_OFFER_TYPES)
551+
.peekable();
552+
let mut experimental_bytes = Vec::with_capacity(
553+
remaining_bytes.len()
554+
- experimental_tlv_stream
555+
.peek()
556+
.map_or(remaining_bytes.len(), |first_record| first_record.start)
557+
);
558+
559+
for record in experimental_tlv_stream {
560+
record.write(&mut experimental_bytes).unwrap();
561+
}
562+
563+
debug_assert_eq!(experimental_bytes.len(), experimental_bytes.capacity());
564+
565+
let tlv_stream = TlvStream::new(&bytes).chain(TlvStream::new(&experimental_bytes));
566+
let tagged_hash = TaggedHash::from_tlv_stream(SIGNATURE_TAG, tlv_stream);
532567

533-
Self { bytes, contents, tagged_hash }
568+
Self { bytes, experimental_bytes, contents, tagged_hash }
534569
}
535570

536571
/// Returns the [`TaggedHash`] of the invoice to sign.
@@ -557,6 +592,15 @@ macro_rules! unsigned_invoice_request_sign_method { (
557592
};
558593
signature_tlv_stream.write(&mut $self.bytes).unwrap();
559594

595+
// Append the experimental bytes after the signature.
596+
debug_assert_eq!(
597+
// The two-byte overallocation results from SIGNATURE_TLV_RECORD_SIZE accommodating TLV
598+
// records with types >= 253.
599+
$self.bytes.len() + $self.experimental_bytes.len() + 2,
600+
$self.bytes.capacity(),
601+
);
602+
$self.bytes.extend_from_slice(&$self.experimental_bytes);
603+
560604
Ok(InvoiceRequest {
561605
#[cfg(not(c_bindings))]
562606
bytes: $self.bytes,
@@ -1072,6 +1116,10 @@ tlv_stream!(InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef<'a>, INVOICE_REQ
10721116
(90, paths: (Vec<BlindedMessagePath>, WithoutLength)),
10731117
});
10741118

1119+
/// Valid type range for experimental invoice_request TLV records.
1120+
pub(super) const EXPERIMENTAL_INVOICE_REQUEST_TYPES: core::ops::Range<u64> =
1121+
2_000_000_000..3_000_000_000;
1122+
10751123
type FullInvoiceRequestTlvStream =
10761124
(PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, SignatureTlvStream);
10771125

@@ -1106,7 +1154,7 @@ impl TryFrom<Vec<u8>> for UnsignedInvoiceRequest {
11061154

11071155
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
11081156
let invoice_request = ParsedMessage::<PartialInvoiceRequestTlvStream>::try_from(bytes)?;
1109-
let ParsedMessage { bytes, tlv_stream } = invoice_request;
1157+
let ParsedMessage { mut bytes, tlv_stream } = invoice_request;
11101158
let (
11111159
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream,
11121160
) = tlv_stream;
@@ -1116,7 +1164,13 @@ impl TryFrom<Vec<u8>> for UnsignedInvoiceRequest {
11161164

11171165
let tagged_hash = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &bytes);
11181166

1119-
Ok(UnsignedInvoiceRequest { bytes, contents, tagged_hash })
1167+
let offset = TlvStream::new(&bytes)
1168+
.range(0..INVOICE_REQUEST_TYPES.end)
1169+
.last()
1170+
.map_or(0, |last_record| last_record.end);
1171+
let experimental_bytes = bytes.split_off(offset);
1172+
1173+
Ok(UnsignedInvoiceRequest { bytes, experimental_bytes, contents, tagged_hash })
11201174
}
11211175
}
11221176

@@ -2306,10 +2360,17 @@ mod tests {
23062360
.request_invoice(vec![1; 32], keys.public_key()).unwrap()
23072361
.build().unwrap();
23082362

2309-
BigSize(UNKNOWN_ODD_TYPE).write(&mut unsigned_invoice_request.bytes).unwrap();
2310-
BigSize(32).write(&mut unsigned_invoice_request.bytes).unwrap();
2311-
[42u8; 32].write(&mut unsigned_invoice_request.bytes).unwrap();
2363+
let mut unknown_bytes = Vec::new();
2364+
BigSize(UNKNOWN_ODD_TYPE).write(&mut unknown_bytes).unwrap();
2365+
BigSize(32).write(&mut unknown_bytes).unwrap();
2366+
[42u8; 32].write(&mut unknown_bytes).unwrap();
23122367

2368+
unsigned_invoice_request.bytes.reserve_exact(
2369+
unsigned_invoice_request.bytes.capacity()
2370+
- unsigned_invoice_request.bytes.len()
2371+
+ unknown_bytes.len(),
2372+
);
2373+
unsigned_invoice_request.bytes.extend_from_slice(&unknown_bytes);
23132374
unsigned_invoice_request.tagged_hash =
23142375
TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &unsigned_invoice_request.bytes);
23152376

@@ -2336,10 +2397,17 @@ mod tests {
23362397
.request_invoice(vec![1; 32], keys.public_key()).unwrap()
23372398
.build().unwrap();
23382399

2339-
BigSize(UNKNOWN_EVEN_TYPE).write(&mut unsigned_invoice_request.bytes).unwrap();
2340-
BigSize(32).write(&mut unsigned_invoice_request.bytes).unwrap();
2341-
[42u8; 32].write(&mut unsigned_invoice_request.bytes).unwrap();
2400+
let mut unknown_bytes = Vec::new();
2401+
BigSize(UNKNOWN_EVEN_TYPE).write(&mut unknown_bytes).unwrap();
2402+
BigSize(32).write(&mut unknown_bytes).unwrap();
2403+
[42u8; 32].write(&mut unknown_bytes).unwrap();
23422404

2405+
unsigned_invoice_request.bytes.reserve_exact(
2406+
unsigned_invoice_request.bytes.capacity()
2407+
- unsigned_invoice_request.bytes.len()
2408+
+ unknown_bytes.len(),
2409+
);
2410+
unsigned_invoice_request.bytes.extend_from_slice(&unknown_bytes);
23432411
unsigned_invoice_request.tagged_hash =
23442412
TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &unsigned_invoice_request.bytes);
23452413

0 commit comments

Comments
 (0)