Skip to content

Commit 94efd75

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 2ddbc18 commit 94efd75

File tree

4 files changed

+105
-48
lines changed

4 files changed

+105
-48
lines changed

lightning/src/offers/invoice.rs

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ use crate::offers::invoice_macros::{invoice_accessors_common, invoice_builder_me
122122
#[cfg(test)]
123123
use crate::offers::invoice_macros::invoice_builder_methods_test;
124124
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};
125+
use crate::offers::merkle::{SignError, SignFn, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, TlvStream, self};
126126
use crate::offers::nonce::Nonce;
127127
use crate::offers::offer::{Amount, OFFER_TYPES, OfferTlvStream, OfferTlvStreamRef, Quantity};
128128
use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError, ParsedMessage};
@@ -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,32 @@ where
491492

492493
impl UnsignedBolt12Invoice {
493494
fn new(invreq_bytes: &[u8], contents: InvoiceContents) -> Self {
495+
const NON_EXPERIMENTAL_TYPES: core::ops::Range<u64> = 0..INVOICE_REQUEST_TYPES.end;
496+
const EXPERIMENTAL_TYPES: core::ops::Range<u64> = 0..0;
497+
498+
let mut bytes = Vec::new();
499+
494500
// Use the invoice_request bytes instead of the invoice_request TLV stream as the latter may
495501
// have contained unknown TLV records, which are not stored in `InvoiceRequestContents` or
496502
// `RefundContents`.
503+
for record in TlvStream::new(invreq_bytes).range(NON_EXPERIMENTAL_TYPES) {
504+
record.write(&mut bytes).unwrap();
505+
}
506+
497507
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);
500508

501-
let mut bytes = Vec::new();
502-
unsigned_tlv_stream.write(&mut bytes).unwrap();
509+
invoice_tlv_stream.write(&mut bytes).unwrap();
503510

504-
let tagged_hash = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &bytes);
511+
let mut experimental_bytes = Vec::new();
505512

506-
Self { bytes, contents, tagged_hash }
513+
for record in TlvStream::new(invreq_bytes).range(EXPERIMENTAL_TYPES) {
514+
record.write(&mut experimental_bytes).unwrap();
515+
}
516+
517+
let tlv_stream = TlvStream::new(&bytes).chain(TlvStream::new(&experimental_bytes));
518+
let tagged_hash = TaggedHash::from_tlv_stream(SIGNATURE_TAG, tlv_stream);
519+
520+
Self { bytes, experimental_bytes, contents, tagged_hash }
507521
}
508522

509523
/// Returns the [`TaggedHash`] of the invoice to sign.
@@ -528,6 +542,9 @@ macro_rules! unsigned_invoice_sign_method { ($self: ident, $self_type: ty $(, $s
528542
};
529543
signature_tlv_stream.write(&mut $self.bytes).unwrap();
530544

545+
// Append the experimental bytes after the signature.
546+
WithoutLength(&$self.experimental_bytes).write(&mut $self.bytes).unwrap();
547+
531548
Ok(Bolt12Invoice {
532549
#[cfg(not(c_bindings))]
533550
bytes: $self.bytes,
@@ -1211,7 +1228,7 @@ impl TryFrom<Vec<u8>> for UnsignedBolt12Invoice {
12111228

12121229
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
12131230
let invoice = ParsedMessage::<PartialInvoiceTlvStream>::try_from(bytes)?;
1214-
let ParsedMessage { bytes, tlv_stream } = invoice;
1231+
let ParsedMessage { mut bytes, tlv_stream } = invoice;
12151232
let (
12161233
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
12171234
) = tlv_stream;
@@ -1221,7 +1238,14 @@ impl TryFrom<Vec<u8>> for UnsignedBolt12Invoice {
12211238

12221239
let tagged_hash = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &bytes);
12231240

1224-
Ok(UnsignedBolt12Invoice { bytes, contents, tagged_hash })
1241+
let mut offset = 0;
1242+
for tlv_record in TlvStream::new(&bytes).range(0..INVOICE_TYPES.end) {
1243+
offset = tlv_record.end;
1244+
}
1245+
1246+
let experimental_bytes = bytes.split_off(offset);
1247+
1248+
Ok(UnsignedBolt12Invoice { bytes, experimental_bytes, contents, tagged_hash })
12251249
}
12261250
}
12271251

lightning/src/offers/invoice_request.rs

Lines changed: 37 additions & 12 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};
7373
use crate::offers::nonce::Nonce;
74-
use crate::offers::offer::{Offer, OfferContents, OfferId, OfferTlvStream, OfferTlvStreamRef};
74+
use crate::offers::offer::{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
}
@@ -518,19 +519,33 @@ where
518519

519520
impl UnsignedInvoiceRequest {
520521
fn new(offer: &Offer, contents: InvoiceRequestContents) -> Self {
522+
let mut bytes = Vec::new();
523+
521524
// Use the offer bytes instead of the offer TLV stream as the offer may have contained
522525
// 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);
526+
let (
527+
payer_tlv_stream, _offer_tlv_stream, invoice_request_tlv_stream,
528+
) = contents.as_tlv_stream();
527529

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

531-
let tagged_hash = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &bytes);
532+
for record in TlvStream::new(&offer.bytes).range(OFFER_TYPES) {
533+
record.write(&mut bytes).unwrap();
534+
}
535+
536+
invoice_request_tlv_stream.write(&mut bytes).unwrap();
537+
538+
const EXPERIMENTAL_OFFER_TYPES: core::ops::Range<u64> = 0..0;
539+
let mut experimental_bytes = Vec::new();
532540

533-
Self { bytes, contents, tagged_hash }
541+
for record in TlvStream::new(&offer.bytes).range(EXPERIMENTAL_OFFER_TYPES) {
542+
record.write(&mut experimental_bytes).unwrap();
543+
}
544+
545+
let tlv_stream = TlvStream::new(&bytes).chain(TlvStream::new(&experimental_bytes));
546+
let tagged_hash = TaggedHash::from_tlv_stream(SIGNATURE_TAG, tlv_stream);
547+
548+
Self { bytes, experimental_bytes, contents, tagged_hash }
534549
}
535550

536551
/// Returns the [`TaggedHash`] of the invoice to sign.
@@ -557,6 +572,9 @@ macro_rules! unsigned_invoice_request_sign_method { (
557572
};
558573
signature_tlv_stream.write(&mut $self.bytes).unwrap();
559574

575+
// Append the experimental bytes after the signature.
576+
WithoutLength(&$self.experimental_bytes).write(&mut $self.bytes).unwrap();
577+
560578
Ok(InvoiceRequest {
561579
#[cfg(not(c_bindings))]
562580
bytes: $self.bytes,
@@ -1106,7 +1124,7 @@ impl TryFrom<Vec<u8>> for UnsignedInvoiceRequest {
11061124

11071125
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
11081126
let invoice_request = ParsedMessage::<PartialInvoiceRequestTlvStream>::try_from(bytes)?;
1109-
let ParsedMessage { bytes, tlv_stream } = invoice_request;
1127+
let ParsedMessage { mut bytes, tlv_stream } = invoice_request;
11101128
let (
11111129
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream,
11121130
) = tlv_stream;
@@ -1116,7 +1134,14 @@ impl TryFrom<Vec<u8>> for UnsignedInvoiceRequest {
11161134

11171135
let tagged_hash = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &bytes);
11181136

1119-
Ok(UnsignedInvoiceRequest { bytes, contents, tagged_hash })
1137+
let mut offset = 0;
1138+
for tlv_record in TlvStream::new(&bytes).range(0..INVOICE_REQUEST_TYPES.end) {
1139+
offset = tlv_record.end;
1140+
}
1141+
1142+
let experimental_bytes = bytes.split_off(offset);
1143+
1144+
Ok(UnsignedInvoiceRequest { bytes, experimental_bytes, contents, tagged_hash })
11201145
}
11211146
}
11221147

lightning/src/offers/merkle.rs

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ fn root_hash<'a, I: core::iter::Iterator<Item = TlvRecord<'a>>>(tlv_stream: I) -
164164
let branch_tag = tagged_hash_engine(sha256::Hash::hash("LnBranch".as_bytes()));
165165

166166
let mut leaves = Vec::new();
167-
for record in TlvStream::skip_signatures(tlv_stream) {
167+
for record in tlv_stream.filter(|record| !SIGNATURE_TYPES.contains(&record.r#type)) {
168168
leaves.push(tagged_hash_from_engine(leaf_tag.clone(), &record.record_bytes));
169169
leaves.push(tagged_hash_from_engine(nonce_tag.clone(), &record.type_bytes));
170170
}
@@ -240,12 +240,6 @@ impl<'a> TlvStream<'a> {
240240
self.skip_while(move |record| !types.contains(&record.r#type))
241241
.take_while(move |record| take_range.contains(&record.r#type))
242242
}
243-
244-
fn skip_signatures(
245-
tlv_stream: impl core::iter::Iterator<Item = TlvRecord<'a>>
246-
) -> impl core::iter::Iterator<Item = TlvRecord<'a>> {
247-
tlv_stream.filter(|record| !SIGNATURE_TYPES.contains(&record.r#type))
248-
}
249243
}
250244

251245
/// A slice into a [`TlvStream`] for a record.
@@ -254,6 +248,7 @@ pub(super) struct TlvRecord<'a> {
254248
type_bytes: &'a [u8],
255249
// The entire TLV record.
256250
pub(super) record_bytes: &'a [u8],
251+
pub(super) end: usize,
257252
}
258253

259254
impl<'a> Iterator for TlvStream<'a> {
@@ -276,32 +271,23 @@ impl<'a> Iterator for TlvStream<'a> {
276271

277272
self.data.set_position(end);
278273

279-
Some(TlvRecord { r#type, type_bytes, record_bytes })
274+
Some(TlvRecord { r#type, type_bytes, record_bytes, end: end as usize })
280275
} else {
281276
None
282277
}
283278
}
284279
}
285280

286-
/// Encoding for a pre-serialized TLV stream that excludes any signature TLV records.
287-
///
288-
/// Panics if the wrapped bytes are not a well-formed TLV stream.
289-
pub(super) struct WithoutSignatures<'a>(pub &'a [u8]);
290-
291-
impl<'a> Writeable for WithoutSignatures<'a> {
281+
impl<'a> Writeable for TlvRecord<'a> {
292282
#[inline]
293283
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
294-
let tlv_stream = TlvStream::new(self.0);
295-
for record in TlvStream::skip_signatures(tlv_stream) {
296-
writer.write_all(record.record_bytes)?;
297-
}
298-
Ok(())
284+
writer.write_all(self.record_bytes)
299285
}
300286
}
301287

302288
#[cfg(test)]
303289
mod tests {
304-
use super::{SIGNATURE_TYPES, TlvStream, WithoutSignatures};
290+
use super::{SIGNATURE_TYPES, TlvStream};
305291

306292
use bitcoin::hashes::{Hash, sha256};
307293
use bitcoin::hex::FromHex;
@@ -411,7 +397,11 @@ mod tests {
411397
.unwrap();
412398

413399
let mut bytes_without_signature = Vec::new();
414-
WithoutSignatures(&invoice_request.bytes).write(&mut bytes_without_signature).unwrap();
400+
let tlv_stream_without_signatures = TlvStream::new(&invoice_request.bytes)
401+
.filter(|record| !SIGNATURE_TYPES.contains(&record.r#type));
402+
for record in tlv_stream_without_signatures {
403+
record.write(&mut bytes_without_signature).unwrap();
404+
}
415405

416406
assert_ne!(bytes_without_signature, invoice_request.bytes);
417407
assert_eq!(

lightning/src/offers/static_invoice.rs

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ impl<'a> StaticInvoiceBuilder<'a> {
170170
/// A semantically valid [`StaticInvoice`] that hasn't been signed.
171171
pub struct UnsignedStaticInvoice {
172172
bytes: Vec<u8>,
173+
experimental_bytes: Vec<u8>,
173174
contents: InvoiceContents,
174175
tagged_hash: TaggedHash,
175176
}
@@ -275,16 +276,30 @@ macro_rules! invoice_accessors_signing_pubkey {
275276

276277
impl UnsignedStaticInvoice {
277278
fn new(offer_bytes: &Vec<u8>, contents: InvoiceContents) -> Self {
278-
let (_, invoice_tlv_stream) = contents.as_tlv_stream();
279-
let offer_bytes = WithoutLength(offer_bytes);
280-
let unsigned_tlv_stream = (offer_bytes, invoice_tlv_stream);
279+
const NON_EXPERIMENTAL_TYPES: core::ops::Range<u64> = OFFER_TYPES;
280+
const EXPERIMENTAL_TYPES: core::ops::Range<u64> = 0..0;
281281

282282
let mut bytes = Vec::new();
283-
unsigned_tlv_stream.write(&mut bytes).unwrap();
284283

285-
let tagged_hash = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &bytes);
284+
// Use the offer bytes instead of the offer TLV stream as the latter may have contained
285+
// unknown TLV records, which are not stored in `InvoiceContents`.
286+
for record in TlvStream::new(offer_bytes).range(NON_EXPERIMENTAL_TYPES) {
287+
record.write(&mut bytes).unwrap();
288+
}
289+
290+
let (_, invoice_tlv_stream) = contents.as_tlv_stream();
291+
invoice_tlv_stream.write(&mut bytes).unwrap();
286292

287-
Self { contents, tagged_hash, bytes }
293+
let mut experimental_bytes = Vec::new();
294+
295+
for record in TlvStream::new(offer_bytes).range(EXPERIMENTAL_TYPES) {
296+
record.write(&mut experimental_bytes).unwrap();
297+
}
298+
299+
let tlv_stream = TlvStream::new(&bytes).chain(TlvStream::new(&experimental_bytes));
300+
let tagged_hash = TaggedHash::from_tlv_stream(SIGNATURE_TAG, tlv_stream);
301+
302+
Self { bytes, experimental_bytes, contents, tagged_hash }
288303
}
289304

290305
/// Signs the [`TaggedHash`] of the invoice using the given function.
@@ -298,6 +313,9 @@ impl UnsignedStaticInvoice {
298313
let signature_tlv_stream = SignatureTlvStreamRef { signature: Some(&signature) };
299314
signature_tlv_stream.write(&mut self.bytes).unwrap();
300315

316+
// Append the experimental bytes after the signature.
317+
WithoutLength(&self.experimental_bytes).write(&mut self.bytes).unwrap();
318+
301319
Ok(StaticInvoice { bytes: self.bytes, contents: self.contents, signature })
302320
}
303321

0 commit comments

Comments
 (0)