Merge pull request #3237 from jkczyz/2024-08-offers-experimental-tlvs

Experimental offer TLVs
This commit is contained in:
Jeffrey Czyz 2024-11-05 11:37:02 -06:00 committed by GitHub
commit cb650a8606
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 1808 additions and 341 deletions

View File

@ -121,10 +121,10 @@ use crate::ln::msgs::DecodeError;
use crate::offers::invoice_macros::{invoice_accessors_common, invoice_builder_methods_common};
#[cfg(test)]
use crate::offers::invoice_macros::invoice_builder_methods_test;
use crate::offers::invoice_request::{INVOICE_REQUEST_PAYER_ID_TYPE, INVOICE_REQUEST_TYPES, IV_BYTES as INVOICE_REQUEST_IV_BYTES, InvoiceRequest, InvoiceRequestContents, InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef};
use crate::offers::merkle::{SignError, SignFn, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, TlvStream, WithoutSignatures, self};
use crate::offers::invoice_request::{EXPERIMENTAL_INVOICE_REQUEST_TYPES, ExperimentalInvoiceRequestTlvStream, ExperimentalInvoiceRequestTlvStreamRef, INVOICE_REQUEST_PAYER_ID_TYPE, INVOICE_REQUEST_TYPES, IV_BYTES as INVOICE_REQUEST_IV_BYTES, InvoiceRequest, InvoiceRequestContents, InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef};
use crate::offers::merkle::{SignError, SignFn, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, TlvStream, self, SIGNATURE_TLV_RECORD_SIZE};
use crate::offers::nonce::Nonce;
use crate::offers::offer::{Amount, OFFER_TYPES, OfferTlvStream, OfferTlvStreamRef, Quantity};
use crate::offers::offer::{Amount, EXPERIMENTAL_OFFER_TYPES, ExperimentalOfferTlvStream, ExperimentalOfferTlvStreamRef, OFFER_TYPES, OfferTlvStream, OfferTlvStreamRef, Quantity};
use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError, ParsedMessage};
use crate::offers::payer::{PAYER_METADATA_TYPE, PayerTlvStream, PayerTlvStreamRef};
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};
@ -363,6 +363,8 @@ macro_rules! invoice_builder_methods { (
InvoiceFields {
payment_paths, created_at, relative_expiry: None, payment_hash, amount_msats,
fallbacks: None, features: Bolt12InvoiceFeatures::empty(), signing_pubkey,
#[cfg(test)]
experimental_baz: None,
}
}
@ -461,6 +463,7 @@ for InvoiceBuilder<'a, DerivedSigningPubkey> {
#[derive(Clone)]
pub struct UnsignedBolt12Invoice {
bytes: Vec<u8>,
experimental_bytes: Vec<u8>,
contents: InvoiceContents,
tagged_hash: TaggedHash,
}
@ -491,19 +494,61 @@ where
impl UnsignedBolt12Invoice {
fn new(invreq_bytes: &[u8], contents: InvoiceContents) -> Self {
// TLV record ranges applicable to invreq_bytes.
const NON_EXPERIMENTAL_TYPES: core::ops::Range<u64> = 0..INVOICE_REQUEST_TYPES.end;
const EXPERIMENTAL_TYPES: core::ops::Range<u64> =
EXPERIMENTAL_OFFER_TYPES.start..EXPERIMENTAL_INVOICE_REQUEST_TYPES.end;
let (_, _, _, invoice_tlv_stream, _, _, experimental_invoice_tlv_stream) =
contents.as_tlv_stream();
// Allocate enough space for the invoice, which will include:
// - all TLV records from `invreq_bytes` except signatures,
// - all invoice-specific TLV records, and
// - a signature TLV record once the invoice is signed.
//
// This assumes both the invoice request and the invoice will each only have one signature
// using SIGNATURE_TYPES.start as the TLV record. Thus, it is accounted for by invreq_bytes.
let mut bytes = Vec::with_capacity(
invreq_bytes.len()
+ invoice_tlv_stream.serialized_length()
+ if contents.is_for_offer() { 0 } else { SIGNATURE_TLV_RECORD_SIZE }
+ experimental_invoice_tlv_stream.serialized_length(),
);
// Use the invoice_request bytes instead of the invoice_request TLV stream as the latter may
// have contained unknown TLV records, which are not stored in `InvoiceRequestContents` or
// `RefundContents`.
let (_, _, _, invoice_tlv_stream) = contents.as_tlv_stream();
let invoice_request_bytes = WithoutSignatures(invreq_bytes);
let unsigned_tlv_stream = (invoice_request_bytes, invoice_tlv_stream);
for record in TlvStream::new(invreq_bytes).range(NON_EXPERIMENTAL_TYPES) {
record.write(&mut bytes).unwrap();
}
let mut bytes = Vec::new();
unsigned_tlv_stream.write(&mut bytes).unwrap();
let remaining_bytes = &invreq_bytes[bytes.len()..];
let tagged_hash = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &bytes);
invoice_tlv_stream.write(&mut bytes).unwrap();
Self { bytes, contents, tagged_hash }
let mut experimental_tlv_stream = TlvStream::new(remaining_bytes)
.range(EXPERIMENTAL_TYPES)
.peekable();
let mut experimental_bytes = Vec::with_capacity(
remaining_bytes.len()
- experimental_tlv_stream
.peek()
.map_or(remaining_bytes.len(), |first_record| first_record.start)
+ experimental_invoice_tlv_stream.serialized_length(),
);
for record in experimental_tlv_stream {
record.write(&mut experimental_bytes).unwrap();
}
experimental_invoice_tlv_stream.write(&mut experimental_bytes).unwrap();
debug_assert_eq!(experimental_bytes.len(), experimental_bytes.capacity());
let tlv_stream = TlvStream::new(&bytes).chain(TlvStream::new(&experimental_bytes));
let tagged_hash = TaggedHash::from_tlv_stream(SIGNATURE_TAG, tlv_stream);
Self { bytes, experimental_bytes, contents, tagged_hash }
}
/// Returns the [`TaggedHash`] of the invoice to sign.
@ -528,6 +573,17 @@ macro_rules! unsigned_invoice_sign_method { ($self: ident, $self_type: ty $(, $s
};
signature_tlv_stream.write(&mut $self.bytes).unwrap();
// Append the experimental bytes after the signature.
debug_assert_eq!(
// The two-byte overallocation results from SIGNATURE_TLV_RECORD_SIZE accommodating TLV
// records with types >= 253.
$self.bytes.len()
+ $self.experimental_bytes.len()
+ if $self.contents.is_for_offer() { 0 } else { 2 },
$self.bytes.capacity(),
);
$self.bytes.extend_from_slice(&$self.experimental_bytes);
Ok(Bolt12Invoice {
#[cfg(not(c_bindings))]
bytes: $self.bytes,
@ -612,6 +668,8 @@ struct InvoiceFields {
fallbacks: Option<Vec<FallbackAddress>>,
features: Bolt12InvoiceFeatures,
signing_pubkey: PublicKey,
#[cfg(test)]
experimental_baz: Option<u64>,
}
macro_rules! invoice_accessors { ($self: ident, $contents: expr) => {
@ -828,7 +886,7 @@ impl Bolt12Invoice {
(&refund.payer.0, REFUND_IV_BYTES_WITH_METADATA)
},
};
self.contents.verify(TlvStream::new(&self.bytes), metadata, key, iv_bytes, secp_ctx)
self.contents.verify(&self.bytes, metadata, key, iv_bytes, secp_ctx)
}
/// Verifies that the invoice was for a request or refund created using the given key by
@ -842,7 +900,8 @@ impl Bolt12Invoice {
InvoiceContents::ForOffer { .. } => INVOICE_REQUEST_IV_BYTES,
InvoiceContents::ForRefund { .. } => REFUND_IV_BYTES_WITHOUT_METADATA,
};
self.contents.verify(TlvStream::new(&self.bytes), &metadata, key, iv_bytes, secp_ctx)
self.contents
.verify(&self.bytes, &metadata, key, iv_bytes, secp_ctx)
.and_then(|extracted_payment_id| (payment_id == extracted_payment_id)
.then(|| payment_id)
.ok_or(())
@ -850,13 +909,19 @@ impl Bolt12Invoice {
}
pub(crate) fn as_tlv_stream(&self) -> FullInvoiceTlvStreamRef {
let (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream) =
self.contents.as_tlv_stream();
let (
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream,
experimental_invoice_tlv_stream,
) = self.contents.as_tlv_stream();
let signature_tlv_stream = SignatureTlvStreamRef {
signature: Some(&self.signature),
};
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
signature_tlv_stream)
(
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
signature_tlv_stream, experimental_offer_tlv_stream,
experimental_invoice_request_tlv_stream, experimental_invoice_tlv_stream,
)
}
pub(crate) fn is_for_refund_without_paths(&self) -> bool {
@ -882,6 +947,13 @@ impl Hash for Bolt12Invoice {
}
impl InvoiceContents {
fn is_for_offer(&self) -> bool {
match self {
InvoiceContents::ForOffer { .. } => true,
InvoiceContents::ForRefund { .. } => false,
}
}
/// Whether the original offer or refund has expired.
#[cfg(feature = "std")]
fn is_offer_or_refund_expired(&self) -> bool {
@ -1086,18 +1158,22 @@ impl InvoiceContents {
}
fn verify<T: secp256k1::Signing>(
&self, tlv_stream: TlvStream<'_>, metadata: &Metadata, key: &ExpandedKey,
iv_bytes: &[u8; IV_LEN], secp_ctx: &Secp256k1<T>
&self, bytes: &[u8], metadata: &Metadata, key: &ExpandedKey, iv_bytes: &[u8; IV_LEN],
secp_ctx: &Secp256k1<T>,
) -> Result<PaymentId, ()> {
let offer_records = tlv_stream.clone().range(OFFER_TYPES);
let invreq_records = tlv_stream.range(INVOICE_REQUEST_TYPES).filter(|record| {
const EXPERIMENTAL_TYPES: core::ops::Range<u64> =
EXPERIMENTAL_OFFER_TYPES.start..EXPERIMENTAL_INVOICE_REQUEST_TYPES.end;
let offer_records = TlvStream::new(bytes).range(OFFER_TYPES);
let invreq_records = TlvStream::new(bytes).range(INVOICE_REQUEST_TYPES).filter(|record| {
match record.r#type {
PAYER_METADATA_TYPE => false, // Should be outside range
INVOICE_REQUEST_PAYER_ID_TYPE => !metadata.derives_payer_keys(),
_ => true,
}
});
let tlv_stream = offer_records.chain(invreq_records);
let experimental_records = TlvStream::new(bytes).range(EXPERIMENTAL_TYPES);
let tlv_stream = offer_records.chain(invreq_records).chain(experimental_records);
let signing_pubkey = self.payer_signing_pubkey();
signer::verify_payer_metadata(
@ -1106,13 +1182,18 @@ impl InvoiceContents {
}
fn as_tlv_stream(&self) -> PartialInvoiceTlvStreamRef {
let (payer, offer, invoice_request) = match self {
let (
payer, offer, invoice_request, experimental_offer, experimental_invoice_request,
) = match self {
InvoiceContents::ForOffer { invoice_request, .. } => invoice_request.as_tlv_stream(),
InvoiceContents::ForRefund { refund, .. } => refund.as_tlv_stream(),
};
let invoice = self.fields().as_tlv_stream();
let (invoice, experimental_invoice) = self.fields().as_tlv_stream();
(payer, offer, invoice_request, invoice)
(
payer, offer, invoice_request, invoice, experimental_offer,
experimental_invoice_request, experimental_invoice,
)
}
}
@ -1160,24 +1241,30 @@ pub(super) fn filter_fallbacks(
}
impl InvoiceFields {
fn as_tlv_stream(&self) -> InvoiceTlvStreamRef {
fn as_tlv_stream(&self) -> (InvoiceTlvStreamRef, ExperimentalInvoiceTlvStreamRef) {
let features = {
if self.features == Bolt12InvoiceFeatures::empty() { None }
else { Some(&self.features) }
};
InvoiceTlvStreamRef {
paths: Some(Iterable(self.payment_paths.iter().map(|path| path.inner_blinded_path()))),
blindedpay: Some(Iterable(self.payment_paths.iter().map(|path| &path.payinfo))),
created_at: Some(self.created_at.as_secs()),
relative_expiry: self.relative_expiry.map(|duration| duration.as_secs() as u32),
payment_hash: Some(&self.payment_hash),
amount: Some(self.amount_msats),
fallbacks: self.fallbacks.as_ref(),
features,
node_id: Some(&self.signing_pubkey),
message_paths: None,
}
(
InvoiceTlvStreamRef {
paths: Some(Iterable(self.payment_paths.iter().map(|path| path.inner_blinded_path()))),
blindedpay: Some(Iterable(self.payment_paths.iter().map(|path| &path.payinfo))),
created_at: Some(self.created_at.as_secs()),
relative_expiry: self.relative_expiry.map(|duration| duration.as_secs() as u32),
payment_hash: Some(&self.payment_hash),
amount: Some(self.amount_msats),
fallbacks: self.fallbacks.as_ref(),
features,
node_id: Some(&self.signing_pubkey),
message_paths: None,
},
ExperimentalInvoiceTlvStreamRef {
#[cfg(test)]
experimental_baz: self.experimental_baz,
},
)
}
}
@ -1211,17 +1298,18 @@ impl TryFrom<Vec<u8>> for UnsignedBolt12Invoice {
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
let invoice = ParsedMessage::<PartialInvoiceTlvStream>::try_from(bytes)?;
let ParsedMessage { bytes, tlv_stream } = invoice;
let (
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
) = tlv_stream;
let contents = InvoiceContents::try_from(
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream)
)?;
let ParsedMessage { mut bytes, tlv_stream } = invoice;
let contents = InvoiceContents::try_from(tlv_stream)?;
let tagged_hash = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &bytes);
Ok(UnsignedBolt12Invoice { bytes, contents, tagged_hash })
let offset = TlvStream::new(&bytes)
.range(0..INVOICE_TYPES.end)
.last()
.map_or(0, |last_record| last_record.end);
let experimental_bytes = bytes.split_off(offset);
Ok(UnsignedBolt12Invoice { bytes, experimental_bytes, contents, tagged_hash })
}
}
@ -1234,7 +1322,10 @@ impl TryFrom<Vec<u8>> for Bolt12Invoice {
}
}
tlv_stream!(InvoiceTlvStream, InvoiceTlvStreamRef, 160..240, {
/// Valid type range for invoice TLV records.
pub(super) const INVOICE_TYPES: core::ops::Range<u64> = 160..240;
tlv_stream!(InvoiceTlvStream, InvoiceTlvStreamRef<'a>, INVOICE_TYPES, {
(160, paths: (Vec<BlindedPath>, WithoutLength, Iterable<'a, BlindedPathIter<'a>, BlindedPath>)),
(162, blindedpay: (Vec<BlindedPayInfo>, WithoutLength, Iterable<'a, BlindedPayInfoIter<'a>, BlindedPayInfo>)),
(164, created_at: (u64, HighZeroBytesDroppedBigSize)),
@ -1245,9 +1336,24 @@ tlv_stream!(InvoiceTlvStream, InvoiceTlvStreamRef, 160..240, {
(174, features: (Bolt12InvoiceFeatures, WithoutLength)),
(176, node_id: PublicKey),
// Only present in `StaticInvoice`s.
(238, message_paths: (Vec<BlindedMessagePath>, WithoutLength)),
(236, message_paths: (Vec<BlindedMessagePath>, WithoutLength)),
});
/// Valid type range for experimental invoice TLV records.
pub(super) const EXPERIMENTAL_INVOICE_TYPES: core::ops::RangeFrom<u64> = 3_000_000_000..;
#[cfg(not(test))]
tlv_stream!(
ExperimentalInvoiceTlvStream, ExperimentalInvoiceTlvStreamRef, EXPERIMENTAL_INVOICE_TYPES, {}
);
#[cfg(test)]
tlv_stream!(
ExperimentalInvoiceTlvStream, ExperimentalInvoiceTlvStreamRef, EXPERIMENTAL_INVOICE_TYPES, {
(3_999_999_999, experimental_baz: (u64, HighZeroBytesDroppedBigSize)),
}
);
pub(super) type BlindedPathIter<'a> = core::iter::Map<
core::slice::Iter<'a, BlindedPaymentPath>,
for<'r> fn(&'r BlindedPaymentPath) -> &'r BlindedPath,
@ -1267,8 +1373,10 @@ pub(super) struct FallbackAddress {
impl_writeable!(FallbackAddress, { version, program });
type FullInvoiceTlvStream =
(PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, InvoiceTlvStream, SignatureTlvStream);
type FullInvoiceTlvStream =(
PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, InvoiceTlvStream, SignatureTlvStream,
ExperimentalOfferTlvStream, ExperimentalInvoiceRequestTlvStream, ExperimentalInvoiceTlvStream,
);
type FullInvoiceTlvStreamRef<'a> = (
PayerTlvStreamRef<'a>,
@ -1276,6 +1384,9 @@ type FullInvoiceTlvStreamRef<'a> = (
InvoiceRequestTlvStreamRef<'a>,
InvoiceTlvStreamRef<'a>,
SignatureTlvStreamRef<'a>,
ExperimentalOfferTlvStreamRef,
ExperimentalInvoiceRequestTlvStreamRef,
ExperimentalInvoiceTlvStreamRef,
);
impl CursorReadable for FullInvoiceTlvStream {
@ -1285,19 +1396,32 @@ impl CursorReadable for FullInvoiceTlvStream {
let invoice_request = CursorReadable::read(r)?;
let invoice = CursorReadable::read(r)?;
let signature = CursorReadable::read(r)?;
let experimental_offer = CursorReadable::read(r)?;
let experimental_invoice_request = CursorReadable::read(r)?;
let experimental_invoice = CursorReadable::read(r)?;
Ok((payer, offer, invoice_request, invoice, signature))
Ok(
(
payer, offer, invoice_request, invoice, signature, experimental_offer,
experimental_invoice_request, experimental_invoice,
)
)
}
}
type PartialInvoiceTlvStream =
(PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, InvoiceTlvStream);
type PartialInvoiceTlvStream = (
PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, InvoiceTlvStream,
ExperimentalOfferTlvStream, ExperimentalInvoiceRequestTlvStream, ExperimentalInvoiceTlvStream,
);
type PartialInvoiceTlvStreamRef<'a> = (
PayerTlvStreamRef<'a>,
OfferTlvStreamRef<'a>,
InvoiceRequestTlvStreamRef<'a>,
InvoiceTlvStreamRef<'a>,
ExperimentalOfferTlvStreamRef,
ExperimentalInvoiceRequestTlvStreamRef,
ExperimentalInvoiceTlvStreamRef,
);
impl CursorReadable for PartialInvoiceTlvStream {
@ -1306,8 +1430,16 @@ impl CursorReadable for PartialInvoiceTlvStream {
let offer = CursorReadable::read(r)?;
let invoice_request = CursorReadable::read(r)?;
let invoice = CursorReadable::read(r)?;
let experimental_offer = CursorReadable::read(r)?;
let experimental_invoice_request = CursorReadable::read(r)?;
let experimental_invoice = CursorReadable::read(r)?;
Ok((payer, offer, invoice_request, invoice))
Ok(
(
payer, offer, invoice_request, invoice, experimental_offer,
experimental_invoice_request, experimental_invoice,
)
)
}
}
@ -1319,9 +1451,16 @@ impl TryFrom<ParsedMessage<FullInvoiceTlvStream>> for Bolt12Invoice {
let (
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
SignatureTlvStream { signature },
experimental_offer_tlv_stream,
experimental_invoice_request_tlv_stream,
experimental_invoice_tlv_stream,
) = tlv_stream;
let contents = InvoiceContents::try_from(
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream)
(
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream,
experimental_invoice_tlv_stream,
)
)?;
let signature = signature.ok_or(
@ -1347,6 +1486,12 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
paths, blindedpay, created_at, relative_expiry, payment_hash, amount, fallbacks,
features, node_id, message_paths,
},
experimental_offer_tlv_stream,
experimental_invoice_request_tlv_stream,
ExperimentalInvoiceTlvStream {
#[cfg(test)]
experimental_baz,
},
) = tlv_stream;
if message_paths.is_some() { return Err(Bolt12SemanticError::UnexpectedPaths) }
@ -1373,18 +1518,26 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
let fields = InvoiceFields {
payment_paths, created_at, relative_expiry, payment_hash, amount_msats, fallbacks,
features, signing_pubkey,
#[cfg(test)]
experimental_baz,
};
check_invoice_signing_pubkey(&fields.signing_pubkey, &offer_tlv_stream)?;
if offer_tlv_stream.issuer_id.is_none() && offer_tlv_stream.paths.is_none() {
let refund = RefundContents::try_from(
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream)
(
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream,
experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream,
)
)?;
Ok(InvoiceContents::ForRefund { refund, fields })
} else {
let invoice_request = InvoiceRequestContents::try_from(
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream)
(
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream,
experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream,
)
)?;
Ok(InvoiceContents::ForOffer { invoice_request, fields })
}
@ -1437,7 +1590,7 @@ pub(super) fn check_invoice_signing_pubkey(
#[cfg(test)]
mod tests {
use super::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, FallbackAddress, FullInvoiceTlvStreamRef, InvoiceTlvStreamRef, SIGNATURE_TAG, UnsignedBolt12Invoice};
use super::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, EXPERIMENTAL_INVOICE_TYPES, ExperimentalInvoiceTlvStreamRef, FallbackAddress, FullInvoiceTlvStreamRef, INVOICE_TYPES, InvoiceTlvStreamRef, SIGNATURE_TAG, UnsignedBolt12Invoice};
use bitcoin::{CompressedPublicKey, WitnessProgram, WitnessVersion};
use bitcoin::constants::ChainHash;
@ -1456,10 +1609,10 @@ mod tests {
use crate::types::features::{Bolt12InvoiceFeatures, InvoiceRequestFeatures, OfferFeatures};
use crate::ln::inbound_payment::ExpandedKey;
use crate::ln::msgs::DecodeError;
use crate::offers::invoice_request::InvoiceRequestTlvStreamRef;
use crate::offers::merkle::{SignError, SignatureTlvStreamRef, TaggedHash, self};
use crate::offers::invoice_request::{ExperimentalInvoiceRequestTlvStreamRef, InvoiceRequestTlvStreamRef};
use crate::offers::merkle::{SignError, SignatureTlvStreamRef, TaggedHash, TlvStream, self};
use crate::offers::nonce::Nonce;
use crate::offers::offer::{Amount, OfferTlvStreamRef, Quantity};
use crate::offers::offer::{Amount, ExperimentalOfferTlvStreamRef, OfferTlvStreamRef, Quantity};
use crate::prelude::*;
#[cfg(not(c_bindings))]
use {
@ -1627,6 +1780,15 @@ mod tests {
message_paths: None,
},
SignatureTlvStreamRef { signature: Some(&invoice.signature()) },
ExperimentalOfferTlvStreamRef {
experimental_foo: None,
},
ExperimentalInvoiceRequestTlvStreamRef {
experimental_bar: None,
},
ExperimentalInvoiceTlvStreamRef {
experimental_baz: None,
},
),
);
@ -1720,6 +1882,15 @@ mod tests {
message_paths: None,
},
SignatureTlvStreamRef { signature: Some(&invoice.signature()) },
ExperimentalOfferTlvStreamRef {
experimental_foo: None,
},
ExperimentalInvoiceRequestTlvStreamRef {
experimental_bar: None,
},
ExperimentalInvoiceTlvStreamRef {
experimental_baz: None,
},
),
);
@ -1813,6 +1984,7 @@ mod tests {
let offer = OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, nonce, &secp_ctx)
.amount_msats(1000)
.path(blinded_path)
.experimental_foo(42)
.build().unwrap();
let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap()
@ -1834,6 +2006,7 @@ mod tests {
let offer = OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, nonce, &secp_ctx)
.amount_msats(1000)
// Omit the path so that node_id is used for the signing pubkey instead of deriving it
.experimental_foo(42)
.build().unwrap();
let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap()
@ -1855,6 +2028,7 @@ mod tests {
let secp_ctx = Secp256k1::new();
let refund = RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap()
.experimental_foo(42)
.build().unwrap();
if let Err(e) = refund
@ -1913,7 +2087,7 @@ mod tests {
.relative_expiry(one_hour.as_secs() as u32)
.build().unwrap()
.sign(recipient_sign).unwrap();
let (_, _, _, tlv_stream, _) = invoice.as_tlv_stream();
let (_, _, _, tlv_stream, _, _, _, _) = invoice.as_tlv_stream();
#[cfg(feature = "std")]
assert!(!invoice.is_expired());
assert_eq!(invoice.relative_expiry(), one_hour);
@ -1929,7 +2103,7 @@ mod tests {
.relative_expiry(one_hour.as_secs() as u32 - 1)
.build().unwrap()
.sign(recipient_sign).unwrap();
let (_, _, _, tlv_stream, _) = invoice.as_tlv_stream();
let (_, _, _, tlv_stream, _, _, _, _) = invoice.as_tlv_stream();
#[cfg(feature = "std")]
assert!(invoice.is_expired());
assert_eq!(invoice.relative_expiry(), one_hour - Duration::from_secs(1));
@ -1948,7 +2122,7 @@ mod tests {
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
.build().unwrap()
.sign(recipient_sign).unwrap();
let (_, _, _, tlv_stream, _) = invoice.as_tlv_stream();
let (_, _, _, tlv_stream, _, _, _, _) = invoice.as_tlv_stream();
assert_eq!(invoice.amount_msats(), 1001);
assert_eq!(tlv_stream.amount, Some(1001));
}
@ -1966,7 +2140,7 @@ mod tests {
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
.build().unwrap()
.sign(recipient_sign).unwrap();
let (_, _, _, tlv_stream, _) = invoice.as_tlv_stream();
let (_, _, _, tlv_stream, _, _, _, _) = invoice.as_tlv_stream();
assert_eq!(invoice.amount_msats(), 2000);
assert_eq!(tlv_stream.amount, Some(2000));
@ -2004,7 +2178,7 @@ mod tests {
.fallback_v1_p2tr_tweaked(&tweaked_pubkey)
.build().unwrap()
.sign(recipient_sign).unwrap();
let (_, _, _, tlv_stream, _) = invoice.as_tlv_stream();
let (_, _, _, tlv_stream, _, _, _, _) = invoice.as_tlv_stream();
assert_eq!(
invoice.fallbacks(),
vec![
@ -2047,7 +2221,7 @@ mod tests {
.allow_mpp()
.build().unwrap()
.sign(recipient_sign).unwrap();
let (_, _, _, tlv_stream, _) = invoice.as_tlv_stream();
let (_, _, _, tlv_stream, _, _, _, _) = invoice.as_tlv_stream();
assert_eq!(invoice.invoice_features(), &features);
assert_eq!(tlv_stream.features, Some(&features));
}
@ -2494,7 +2668,217 @@ mod tests {
}
#[test]
fn fails_parsing_invoice_with_extra_tlv_records() {
fn parses_invoice_with_unknown_tlv_records() {
const UNKNOWN_ODD_TYPE: u64 = INVOICE_TYPES.end - 1;
assert!(UNKNOWN_ODD_TYPE % 2 == 1);
let secp_ctx = Secp256k1::new();
let keys = Keypair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
let mut unsigned_invoice = OfferBuilder::new(keys.public_key())
.amount_msats(1000)
.build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap()
.sign(payer_sign).unwrap()
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
.build().unwrap();
let mut unknown_bytes = Vec::new();
BigSize(UNKNOWN_ODD_TYPE).write(&mut unknown_bytes).unwrap();
BigSize(32).write(&mut unknown_bytes).unwrap();
[42u8; 32].write(&mut unknown_bytes).unwrap();
unsigned_invoice.bytes.reserve_exact(
unsigned_invoice.bytes.capacity() - unsigned_invoice.bytes.len() + unknown_bytes.len(),
);
unsigned_invoice.bytes.extend_from_slice(&unknown_bytes);
unsigned_invoice.tagged_hash =
TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &unsigned_invoice.bytes);
let invoice = unsigned_invoice
.sign(|message: &UnsignedBolt12Invoice|
Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
)
.unwrap();
let mut encoded_invoice = Vec::new();
invoice.write(&mut encoded_invoice).unwrap();
match Bolt12Invoice::try_from(encoded_invoice.clone()) {
Ok(invoice) => assert_eq!(invoice.bytes, encoded_invoice),
Err(e) => panic!("error parsing invoice: {:?}", e),
}
const UNKNOWN_EVEN_TYPE: u64 = INVOICE_TYPES.end - 2;
assert!(UNKNOWN_EVEN_TYPE % 2 == 0);
let mut unsigned_invoice = OfferBuilder::new(keys.public_key())
.amount_msats(1000)
.build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap()
.sign(payer_sign).unwrap()
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
.build().unwrap();
let mut unknown_bytes = Vec::new();
BigSize(UNKNOWN_EVEN_TYPE).write(&mut unknown_bytes).unwrap();
BigSize(32).write(&mut unknown_bytes).unwrap();
[42u8; 32].write(&mut unknown_bytes).unwrap();
unsigned_invoice.bytes.reserve_exact(
unsigned_invoice.bytes.capacity() - unsigned_invoice.bytes.len() + unknown_bytes.len(),
);
unsigned_invoice.bytes.extend_from_slice(&unknown_bytes);
unsigned_invoice.tagged_hash =
TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &unsigned_invoice.bytes);
let invoice = unsigned_invoice
.sign(|message: &UnsignedBolt12Invoice|
Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
)
.unwrap();
let mut encoded_invoice = Vec::new();
invoice.write(&mut encoded_invoice).unwrap();
match Bolt12Invoice::try_from(encoded_invoice) {
Ok(_) => panic!("expected error"),
Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::UnknownRequiredFeature)),
}
}
#[test]
fn parses_invoice_with_experimental_tlv_records() {
let secp_ctx = Secp256k1::new();
let keys = Keypair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
let invoice = OfferBuilder::new(keys.public_key())
.amount_msats(1000)
.build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap()
.sign(payer_sign).unwrap()
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
.experimental_baz(42)
.build().unwrap()
.sign(|message: &UnsignedBolt12Invoice|
Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
)
.unwrap();
let mut encoded_invoice = Vec::new();
invoice.write(&mut encoded_invoice).unwrap();
assert!(Bolt12Invoice::try_from(encoded_invoice).is_ok());
const UNKNOWN_ODD_TYPE: u64 = EXPERIMENTAL_INVOICE_TYPES.start + 1;
assert!(UNKNOWN_ODD_TYPE % 2 == 1);
let mut unsigned_invoice = OfferBuilder::new(keys.public_key())
.amount_msats(1000)
.build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap()
.sign(payer_sign).unwrap()
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
.build().unwrap();
let mut unknown_bytes = Vec::new();
BigSize(UNKNOWN_ODD_TYPE).write(&mut unknown_bytes).unwrap();
BigSize(32).write(&mut unknown_bytes).unwrap();
[42u8; 32].write(&mut unknown_bytes).unwrap();
unsigned_invoice.bytes.reserve_exact(
unsigned_invoice.bytes.capacity() - unsigned_invoice.bytes.len() + unknown_bytes.len(),
);
unsigned_invoice.experimental_bytes.extend_from_slice(&unknown_bytes);
let tlv_stream = TlvStream::new(&unsigned_invoice.bytes)
.chain(TlvStream::new(&unsigned_invoice.experimental_bytes));
unsigned_invoice.tagged_hash = TaggedHash::from_tlv_stream(SIGNATURE_TAG, tlv_stream);
let invoice = unsigned_invoice
.sign(|message: &UnsignedBolt12Invoice|
Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
)
.unwrap();
let mut encoded_invoice = Vec::new();
invoice.write(&mut encoded_invoice).unwrap();
match Bolt12Invoice::try_from(encoded_invoice.clone()) {
Ok(invoice) => assert_eq!(invoice.bytes, encoded_invoice),
Err(e) => panic!("error parsing invoice: {:?}", e),
}
const UNKNOWN_EVEN_TYPE: u64 = EXPERIMENTAL_INVOICE_TYPES.start;
assert!(UNKNOWN_EVEN_TYPE % 2 == 0);
let mut unsigned_invoice = OfferBuilder::new(keys.public_key())
.amount_msats(1000)
.build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap()
.sign(payer_sign).unwrap()
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
.build().unwrap();
let mut unknown_bytes = Vec::new();
BigSize(UNKNOWN_EVEN_TYPE).write(&mut unknown_bytes).unwrap();
BigSize(32).write(&mut unknown_bytes).unwrap();
[42u8; 32].write(&mut unknown_bytes).unwrap();
unsigned_invoice.bytes.reserve_exact(
unsigned_invoice.bytes.capacity() - unsigned_invoice.bytes.len() + unknown_bytes.len(),
);
unsigned_invoice.experimental_bytes.extend_from_slice(&unknown_bytes);
let tlv_stream = TlvStream::new(&unsigned_invoice.bytes)
.chain(TlvStream::new(&unsigned_invoice.experimental_bytes));
unsigned_invoice.tagged_hash = TaggedHash::from_tlv_stream(SIGNATURE_TAG, tlv_stream);
let invoice = unsigned_invoice
.sign(|message: &UnsignedBolt12Invoice|
Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
)
.unwrap();
let mut encoded_invoice = Vec::new();
invoice.write(&mut encoded_invoice).unwrap();
match Bolt12Invoice::try_from(encoded_invoice) {
Ok(_) => panic!("expected error"),
Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::UnknownRequiredFeature)),
}
let invoice = OfferBuilder::new(keys.public_key())
.amount_msats(1000)
.build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap()
.sign(payer_sign).unwrap()
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
.build().unwrap()
.sign(|message: &UnsignedBolt12Invoice|
Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
)
.unwrap();
let mut encoded_invoice = Vec::new();
invoice.write(&mut encoded_invoice).unwrap();
BigSize(UNKNOWN_ODD_TYPE).write(&mut encoded_invoice).unwrap();
BigSize(32).write(&mut encoded_invoice).unwrap();
[42u8; 32].write(&mut encoded_invoice).unwrap();
match Bolt12Invoice::try_from(encoded_invoice) {
Ok(_) => panic!("expected error"),
Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSignature(secp256k1::Error::IncorrectSignature)),
}
}
#[test]
fn fails_parsing_invoice_with_out_of_range_tlv_records() {
let invoice = OfferBuilder::new(recipient_pubkey())
.amount_msats(1000)
.build().unwrap()

View File

@ -95,6 +95,11 @@ macro_rules! invoice_builder_methods_test { (
$return_value
}
#[cfg_attr(c_bindings, allow(dead_code))]
pub(super) fn experimental_baz($($self_mut)* $self: $self_type, experimental_baz: u64) -> $return_type {
$invoice_fields.experimental_baz = Some(experimental_baz);
$return_value
}
} }
macro_rules! invoice_accessors_common { ($self: ident, $contents: expr, $invoice_type: ty) => {

View File

@ -69,9 +69,9 @@ use crate::ln::channelmanager::PaymentId;
use crate::types::features::InvoiceRequestFeatures;
use crate::ln::inbound_payment::{ExpandedKey, IV_LEN};
use crate::ln::msgs::DecodeError;
use crate::offers::merkle::{SignError, SignFn, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, self};
use crate::offers::merkle::{SignError, SignFn, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, TlvStream, self, SIGNATURE_TLV_RECORD_SIZE};
use crate::offers::nonce::Nonce;
use crate::offers::offer::{Offer, OfferContents, OfferId, OfferTlvStream, OfferTlvStreamRef};
use crate::offers::offer::{EXPERIMENTAL_OFFER_TYPES, ExperimentalOfferTlvStream, ExperimentalOfferTlvStreamRef, OFFER_TYPES, Offer, OfferContents, OfferId, OfferTlvStream, OfferTlvStreamRef};
use crate::offers::parse::{Bolt12ParseError, ParsedMessage, Bolt12SemanticError};
use crate::offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef};
use crate::offers::signer::{Metadata, MetadataMaterial};
@ -241,6 +241,8 @@ macro_rules! invoice_request_builder_methods { (
InvoiceRequestContentsWithoutPayerSigningPubkey {
payer: PayerContents(metadata), offer, chain: None, amount_msats: None,
features: InvoiceRequestFeatures::empty(), quantity: None, payer_note: None,
#[cfg(test)]
experimental_bar: None,
}
}
@ -404,6 +406,12 @@ macro_rules! invoice_request_builder_test_methods { (
$return_value
}
#[cfg_attr(c_bindings, allow(dead_code))]
pub(super) fn experimental_bar($($self_mut)* $self: $self_type, experimental_bar: u64) -> $return_type {
$self.invoice_request.experimental_bar = Some(experimental_bar);
$return_value
}
#[cfg_attr(c_bindings, allow(dead_code))]
pub(super) fn build_unchecked($self: $self_type) -> UnsignedInvoiceRequest {
$self.build_without_checks().0
@ -488,6 +496,7 @@ for InvoiceRequestBuilder<'a, 'b, DerivedPayerSigningPubkey, secp256k1::All> {
#[derive(Clone)]
pub struct UnsignedInvoiceRequest {
bytes: Vec<u8>,
experimental_bytes: Vec<u8>,
contents: InvoiceRequestContents,
tagged_hash: TaggedHash,
}
@ -520,17 +529,55 @@ impl UnsignedInvoiceRequest {
fn new(offer: &Offer, contents: InvoiceRequestContents) -> Self {
// Use the offer bytes instead of the offer TLV stream as the offer may have contained
// unknown TLV records, which are not stored in `OfferContents`.
let (payer_tlv_stream, _offer_tlv_stream, invoice_request_tlv_stream) =
contents.as_tlv_stream();
let offer_bytes = WithoutLength(&offer.bytes);
let unsigned_tlv_stream = (payer_tlv_stream, offer_bytes, invoice_request_tlv_stream);
let (
payer_tlv_stream, _offer_tlv_stream, invoice_request_tlv_stream,
_experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream,
) = contents.as_tlv_stream();
let mut bytes = Vec::new();
unsigned_tlv_stream.write(&mut bytes).unwrap();
// Allocate enough space for the invoice_request, which will include:
// - all TLV records from `offer.bytes`,
// - all invoice_request-specific TLV records, and
// - a signature TLV record once the invoice_request is signed.
let mut bytes = Vec::with_capacity(
offer.bytes.len()
+ payer_tlv_stream.serialized_length()
+ invoice_request_tlv_stream.serialized_length()
+ SIGNATURE_TLV_RECORD_SIZE
+ experimental_invoice_request_tlv_stream.serialized_length(),
);
let tagged_hash = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &bytes);
payer_tlv_stream.write(&mut bytes).unwrap();
Self { bytes, contents, tagged_hash }
for record in TlvStream::new(&offer.bytes).range(OFFER_TYPES) {
record.write(&mut bytes).unwrap();
}
let remaining_bytes = &offer.bytes[bytes.len() - payer_tlv_stream.serialized_length()..];
invoice_request_tlv_stream.write(&mut bytes).unwrap();
let mut experimental_tlv_stream = TlvStream::new(remaining_bytes)
.range(EXPERIMENTAL_OFFER_TYPES)
.peekable();
let mut experimental_bytes = Vec::with_capacity(
remaining_bytes.len()
- experimental_tlv_stream
.peek()
.map_or(remaining_bytes.len(), |first_record| first_record.start)
+ experimental_invoice_request_tlv_stream.serialized_length(),
);
for record in experimental_tlv_stream {
record.write(&mut experimental_bytes).unwrap();
}
experimental_invoice_request_tlv_stream.write(&mut experimental_bytes).unwrap();
debug_assert_eq!(experimental_bytes.len(), experimental_bytes.capacity());
let tlv_stream = TlvStream::new(&bytes).chain(TlvStream::new(&experimental_bytes));
let tagged_hash = TaggedHash::from_tlv_stream(SIGNATURE_TAG, tlv_stream);
Self { bytes, experimental_bytes, contents, tagged_hash }
}
/// Returns the [`TaggedHash`] of the invoice to sign.
@ -557,6 +604,15 @@ macro_rules! unsigned_invoice_request_sign_method { (
};
signature_tlv_stream.write(&mut $self.bytes).unwrap();
// Append the experimental bytes after the signature.
debug_assert_eq!(
// The two-byte overallocation results from SIGNATURE_TLV_RECORD_SIZE accommodating TLV
// records with types >= 253.
$self.bytes.len() + $self.experimental_bytes.len() + 2,
$self.bytes.capacity(),
);
$self.bytes.extend_from_slice(&$self.experimental_bytes);
Ok(InvoiceRequest {
#[cfg(not(c_bindings))]
bytes: $self.bytes,
@ -643,6 +699,8 @@ pub(super) struct InvoiceRequestContentsWithoutPayerSigningPubkey {
features: InvoiceRequestFeatures,
quantity: Option<u64>,
payer_note: Option<String>,
#[cfg(test)]
experimental_bar: Option<u64>,
}
macro_rules! invoice_request_accessors { ($self: ident, $contents: expr) => {
@ -863,12 +921,18 @@ impl InvoiceRequest {
}
pub(crate) fn as_tlv_stream(&self) -> FullInvoiceRequestTlvStreamRef {
let (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream) =
self.contents.as_tlv_stream();
let (
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream,
experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream,
) = self.contents.as_tlv_stream();
let signature_tlv_stream = SignatureTlvStreamRef {
signature: Some(&self.signature),
};
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, signature_tlv_stream)
(
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream,
signature_tlv_stream, experimental_offer_tlv_stream,
experimental_invoice_request_tlv_stream,
)
}
}
@ -940,7 +1004,9 @@ impl VerifiedInvoiceRequest {
let InvoiceRequestContents {
payer_signing_pubkey,
inner: InvoiceRequestContentsWithoutPayerSigningPubkey {
payer: _, offer: _, chain: _, amount_msats: _, features: _, quantity, payer_note
payer: _, offer: _, chain: _, amount_msats: _, features: _, quantity, payer_note,
#[cfg(test)]
experimental_bar: _,
},
} = &self.inner.contents;
@ -984,9 +1050,10 @@ impl InvoiceRequestContents {
}
pub(super) fn as_tlv_stream(&self) -> PartialInvoiceRequestTlvStreamRef {
let (payer, offer, mut invoice_request) = self.inner.as_tlv_stream();
let (payer, offer, mut invoice_request, experimental_offer, experimental_invoice_request) =
self.inner.as_tlv_stream();
invoice_request.payer_id = Some(&self.payer_signing_pubkey);
(payer, offer, invoice_request)
(payer, offer, invoice_request, experimental_offer, experimental_invoice_request)
}
}
@ -1004,7 +1071,7 @@ impl InvoiceRequestContentsWithoutPayerSigningPubkey {
metadata: self.payer.0.as_bytes(),
};
let offer = self.offer.as_tlv_stream();
let (offer, experimental_offer) = self.offer.as_tlv_stream();
let features = {
if self.features == InvoiceRequestFeatures::empty() { None }
@ -1021,7 +1088,12 @@ impl InvoiceRequestContentsWithoutPayerSigningPubkey {
paths: None,
};
(payer, offer, invoice_request)
let experimental_invoice_request = ExperimentalInvoiceRequestTlvStreamRef {
#[cfg(test)]
experimental_bar: self.experimental_bar,
};
(payer, offer, invoice_request, experimental_offer, experimental_invoice_request)
}
}
@ -1061,7 +1133,7 @@ pub(super) const INVOICE_REQUEST_PAYER_ID_TYPE: u64 = 88;
// This TLV stream is used for both InvoiceRequest and Refund, but not all TLV records are valid for
// InvoiceRequest as noted below.
tlv_stream!(InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef, INVOICE_REQUEST_TYPES, {
tlv_stream!(InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef<'a>, INVOICE_REQUEST_TYPES, {
(80, chain: ChainHash),
(82, amount: (u64, HighZeroBytesDroppedBigSize)),
(84, features: (InvoiceRequestFeatures, WithoutLength)),
@ -1072,14 +1144,36 @@ tlv_stream!(InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef, INVOICE_REQUEST
(90, paths: (Vec<BlindedMessagePath>, WithoutLength)),
});
type FullInvoiceRequestTlvStream =
(PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, SignatureTlvStream);
/// Valid type range for experimental invoice_request TLV records.
pub(super) const EXPERIMENTAL_INVOICE_REQUEST_TYPES: core::ops::Range<u64> =
2_000_000_000..3_000_000_000;
#[cfg(not(test))]
tlv_stream!(
ExperimentalInvoiceRequestTlvStream, ExperimentalInvoiceRequestTlvStreamRef,
EXPERIMENTAL_INVOICE_REQUEST_TYPES, {}
);
#[cfg(test)]
tlv_stream!(
ExperimentalInvoiceRequestTlvStream, ExperimentalInvoiceRequestTlvStreamRef,
EXPERIMENTAL_INVOICE_REQUEST_TYPES, {
(2_999_999_999, experimental_bar: (u64, HighZeroBytesDroppedBigSize)),
}
);
type FullInvoiceRequestTlvStream = (
PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, SignatureTlvStream,
ExperimentalOfferTlvStream, ExperimentalInvoiceRequestTlvStream,
);
type FullInvoiceRequestTlvStreamRef<'a> = (
PayerTlvStreamRef<'a>,
OfferTlvStreamRef<'a>,
InvoiceRequestTlvStreamRef<'a>,
SignatureTlvStreamRef<'a>,
ExperimentalOfferTlvStreamRef,
ExperimentalInvoiceRequestTlvStreamRef,
);
impl CursorReadable for FullInvoiceRequestTlvStream {
@ -1088,17 +1182,29 @@ impl CursorReadable for FullInvoiceRequestTlvStream {
let offer = CursorReadable::read(r)?;
let invoice_request = CursorReadable::read(r)?;
let signature = CursorReadable::read(r)?;
let experimental_offer = CursorReadable::read(r)?;
let experimental_invoice_request = CursorReadable::read(r)?;
Ok((payer, offer, invoice_request, signature))
Ok(
(
payer, offer, invoice_request, signature, experimental_offer,
experimental_invoice_request,
)
)
}
}
type PartialInvoiceRequestTlvStream = (PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream);
type PartialInvoiceRequestTlvStream = (
PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, ExperimentalOfferTlvStream,
ExperimentalInvoiceRequestTlvStream,
);
type PartialInvoiceRequestTlvStreamRef<'a> = (
PayerTlvStreamRef<'a>,
OfferTlvStreamRef<'a>,
InvoiceRequestTlvStreamRef<'a>,
ExperimentalOfferTlvStreamRef,
ExperimentalInvoiceRequestTlvStreamRef,
);
impl TryFrom<Vec<u8>> for UnsignedInvoiceRequest {
@ -1106,17 +1212,18 @@ impl TryFrom<Vec<u8>> for UnsignedInvoiceRequest {
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
let invoice_request = ParsedMessage::<PartialInvoiceRequestTlvStream>::try_from(bytes)?;
let ParsedMessage { bytes, tlv_stream } = invoice_request;
let (
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream,
) = tlv_stream;
let contents = InvoiceRequestContents::try_from(
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream)
)?;
let ParsedMessage { mut bytes, tlv_stream } = invoice_request;
let contents = InvoiceRequestContents::try_from(tlv_stream)?;
let tagged_hash = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &bytes);
Ok(UnsignedInvoiceRequest { bytes, contents, tagged_hash })
let offset = TlvStream::new(&bytes)
.range(0..INVOICE_REQUEST_TYPES.end)
.last()
.map_or(0, |last_record| last_record.end);
let experimental_bytes = bytes.split_off(offset);
Ok(UnsignedInvoiceRequest { bytes, experimental_bytes, contents, tagged_hash })
}
}
@ -1129,9 +1236,14 @@ impl TryFrom<Vec<u8>> for InvoiceRequest {
let (
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream,
SignatureTlvStream { signature },
experimental_offer_tlv_stream,
experimental_invoice_request_tlv_stream,
) = tlv_stream;
let contents = InvoiceRequestContents::try_from(
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream)
(
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream,
experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream,
)
)?;
let signature = match signature {
@ -1155,13 +1267,18 @@ impl TryFrom<PartialInvoiceRequestTlvStream> for InvoiceRequestContents {
InvoiceRequestTlvStream {
chain, amount, features, quantity, payer_id, payer_note, paths,
},
experimental_offer_tlv_stream,
ExperimentalInvoiceRequestTlvStream {
#[cfg(test)]
experimental_bar,
},
) = tlv_stream;
let payer = match metadata {
None => return Err(Bolt12SemanticError::MissingPayerMetadata),
Some(metadata) => PayerContents(Metadata::Bytes(metadata)),
};
let offer = OfferContents::try_from(offer_tlv_stream)?;
let offer = OfferContents::try_from((offer_tlv_stream, experimental_offer_tlv_stream))?;
if !offer.supports_chain(chain.unwrap_or_else(|| offer.implied_chain())) {
return Err(Bolt12SemanticError::UnsupportedChain);
@ -1188,6 +1305,8 @@ impl TryFrom<PartialInvoiceRequestTlvStream> for InvoiceRequestContents {
Ok(InvoiceRequestContents {
inner: InvoiceRequestContentsWithoutPayerSigningPubkey {
payer, offer, chain, amount_msats: amount, features, quantity, payer_note,
#[cfg(test)]
experimental_bar,
},
payer_signing_pubkey,
})
@ -1242,7 +1361,7 @@ impl Readable for InvoiceRequestFields {
#[cfg(test)]
mod tests {
use super::{InvoiceRequest, InvoiceRequestFields, InvoiceRequestTlvStreamRef, PAYER_NOTE_LIMIT, SIGNATURE_TAG, UnsignedInvoiceRequest};
use super::{EXPERIMENTAL_INVOICE_REQUEST_TYPES, ExperimentalInvoiceRequestTlvStreamRef, INVOICE_REQUEST_TYPES, InvoiceRequest, InvoiceRequestFields, InvoiceRequestTlvStreamRef, PAYER_NOTE_LIMIT, SIGNATURE_TAG, UnsignedInvoiceRequest};
use bitcoin::constants::ChainHash;
use bitcoin::network::Network;
@ -1256,9 +1375,9 @@ mod tests {
use crate::ln::inbound_payment::ExpandedKey;
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
use crate::offers::invoice::{Bolt12Invoice, SIGNATURE_TAG as INVOICE_SIGNATURE_TAG};
use crate::offers::merkle::{SignError, SignatureTlvStreamRef, TaggedHash, self};
use crate::offers::merkle::{SignError, SignatureTlvStreamRef, TaggedHash, TlvStream, self};
use crate::offers::nonce::Nonce;
use crate::offers::offer::{Amount, OfferTlvStreamRef, Quantity};
use crate::offers::offer::{Amount, ExperimentalOfferTlvStreamRef, OfferTlvStreamRef, Quantity};
#[cfg(not(c_bindings))]
use {
crate::offers::offer::OfferBuilder,
@ -1367,6 +1486,12 @@ mod tests {
paths: None,
},
SignatureTlvStreamRef { signature: Some(&invoice_request.signature()) },
ExperimentalOfferTlvStreamRef {
experimental_foo: None,
},
ExperimentalInvoiceRequestTlvStreamRef {
experimental_bar: None,
},
),
);
@ -1414,16 +1539,19 @@ mod tests {
let offer = OfferBuilder::new(recipient_pubkey())
.amount_msats(1000)
.experimental_foo(42)
.build().unwrap();
let invoice_request = offer
.request_invoice_deriving_metadata(signing_pubkey, &expanded_key, nonce, payment_id)
.unwrap()
.experimental_bar(42)
.build().unwrap()
.sign(payer_sign).unwrap();
assert_eq!(invoice_request.payer_signing_pubkey(), payer_pubkey());
let invoice = invoice_request.respond_with_no_std(payment_paths(), payment_hash(), now())
.unwrap()
.experimental_baz(42)
.build().unwrap()
.sign(recipient_sign).unwrap();
match invoice.verify_using_metadata(&expanded_key, &secp_ctx) {
@ -1437,22 +1565,29 @@ mod tests {
// Fails verification with altered fields
let (
payer_tlv_stream, offer_tlv_stream, mut invoice_request_tlv_stream,
mut invoice_tlv_stream, mut signature_tlv_stream
mut invoice_tlv_stream, mut signature_tlv_stream, experimental_offer_tlv_stream,
experimental_invoice_request_tlv_stream, experimental_invoice_tlv_stream,
) = invoice.as_tlv_stream();
invoice_request_tlv_stream.amount = Some(2000);
invoice_tlv_stream.amount = Some(2000);
let tlv_stream =
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream);
let experimental_tlv_stream = (
experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream,
experimental_invoice_tlv_stream,
);
let mut bytes = Vec::new();
tlv_stream.write(&mut bytes).unwrap();
(&tlv_stream, &experimental_tlv_stream).write(&mut bytes).unwrap();
let message = TaggedHash::from_valid_tlv_stream_bytes(INVOICE_SIGNATURE_TAG, &bytes);
let signature = merkle::sign_message(recipient_sign, &message, recipient_pubkey()).unwrap();
signature_tlv_stream.signature = Some(&signature);
let mut encoded_invoice = bytes;
signature_tlv_stream.write(&mut encoded_invoice).unwrap();
let mut encoded_invoice = Vec::new();
(tlv_stream, signature_tlv_stream, experimental_tlv_stream)
.write(&mut encoded_invoice)
.unwrap();
let invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap();
assert!(invoice.verify_using_metadata(&expanded_key, &secp_ctx).is_err());
@ -1460,22 +1595,29 @@ mod tests {
// Fails verification with altered metadata
let (
mut payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
mut signature_tlv_stream
mut signature_tlv_stream, experimental_offer_tlv_stream,
experimental_invoice_request_tlv_stream, experimental_invoice_tlv_stream,
) = invoice.as_tlv_stream();
let metadata = payer_tlv_stream.metadata.unwrap().iter().copied().rev().collect();
payer_tlv_stream.metadata = Some(&metadata);
let tlv_stream =
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream);
let experimental_tlv_stream = (
experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream,
experimental_invoice_tlv_stream,
);
let mut bytes = Vec::new();
tlv_stream.write(&mut bytes).unwrap();
(&tlv_stream, &experimental_tlv_stream).write(&mut bytes).unwrap();
let message = TaggedHash::from_valid_tlv_stream_bytes(INVOICE_SIGNATURE_TAG, &bytes);
let signature = merkle::sign_message(recipient_sign, &message, recipient_pubkey()).unwrap();
signature_tlv_stream.signature = Some(&signature);
let mut encoded_invoice = bytes;
signature_tlv_stream.write(&mut encoded_invoice).unwrap();
let mut encoded_invoice = Vec::new();
(tlv_stream, signature_tlv_stream, experimental_tlv_stream)
.write(&mut encoded_invoice)
.unwrap();
let invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap();
assert!(invoice.verify_using_metadata(&expanded_key, &secp_ctx).is_err());
@ -1491,15 +1633,18 @@ mod tests {
let offer = OfferBuilder::new(recipient_pubkey())
.amount_msats(1000)
.experimental_foo(42)
.build().unwrap();
let invoice_request = offer
.request_invoice_deriving_signing_pubkey(&expanded_key, nonce, &secp_ctx, payment_id)
.unwrap()
.experimental_bar(42)
.build_and_sign()
.unwrap();
let invoice = invoice_request.respond_with_no_std(payment_paths(), payment_hash(), now())
.unwrap()
.experimental_baz(42)
.build().unwrap()
.sign(recipient_sign).unwrap();
assert!(invoice.verify_using_metadata(&expanded_key, &secp_ctx).is_err());
@ -1510,22 +1655,29 @@ mod tests {
// Fails verification with altered fields
let (
payer_tlv_stream, offer_tlv_stream, mut invoice_request_tlv_stream,
mut invoice_tlv_stream, mut signature_tlv_stream
mut invoice_tlv_stream, mut signature_tlv_stream, experimental_offer_tlv_stream,
experimental_invoice_request_tlv_stream, experimental_invoice_tlv_stream,
) = invoice.as_tlv_stream();
invoice_request_tlv_stream.amount = Some(2000);
invoice_tlv_stream.amount = Some(2000);
let tlv_stream =
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream);
let experimental_tlv_stream = (
experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream,
experimental_invoice_tlv_stream,
);
let mut bytes = Vec::new();
tlv_stream.write(&mut bytes).unwrap();
(&tlv_stream, &experimental_tlv_stream).write(&mut bytes).unwrap();
let message = TaggedHash::from_valid_tlv_stream_bytes(INVOICE_SIGNATURE_TAG, &bytes);
let signature = merkle::sign_message(recipient_sign, &message, recipient_pubkey()).unwrap();
signature_tlv_stream.signature = Some(&signature);
let mut encoded_invoice = bytes;
signature_tlv_stream.write(&mut encoded_invoice).unwrap();
let mut encoded_invoice = Vec::new();
(tlv_stream, signature_tlv_stream, experimental_tlv_stream)
.write(&mut encoded_invoice)
.unwrap();
let invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap();
assert!(
@ -1535,22 +1687,29 @@ mod tests {
// Fails verification with altered payer id
let (
payer_tlv_stream, offer_tlv_stream, mut invoice_request_tlv_stream, invoice_tlv_stream,
mut signature_tlv_stream
mut signature_tlv_stream, experimental_offer_tlv_stream,
experimental_invoice_request_tlv_stream, experimental_invoice_tlv_stream,
) = invoice.as_tlv_stream();
let payer_id = pubkey(1);
invoice_request_tlv_stream.payer_id = Some(&payer_id);
let tlv_stream =
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream);
let experimental_tlv_stream = (
experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream,
experimental_invoice_tlv_stream,
);
let mut bytes = Vec::new();
tlv_stream.write(&mut bytes).unwrap();
(&tlv_stream, &experimental_tlv_stream).write(&mut bytes).unwrap();
let message = TaggedHash::from_valid_tlv_stream_bytes(INVOICE_SIGNATURE_TAG, &bytes);
let signature = merkle::sign_message(recipient_sign, &message, recipient_pubkey()).unwrap();
signature_tlv_stream.signature = Some(&signature);
let mut encoded_invoice = bytes;
signature_tlv_stream.write(&mut encoded_invoice).unwrap();
let mut encoded_invoice = Vec::new();
(tlv_stream, signature_tlv_stream, experimental_tlv_stream)
.write(&mut encoded_invoice)
.unwrap();
let invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap();
assert!(
@ -1570,7 +1729,7 @@ mod tests {
.chain(Network::Bitcoin).unwrap()
.build().unwrap()
.sign(payer_sign).unwrap();
let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream();
let (_, _, tlv_stream, _, _, _) = invoice_request.as_tlv_stream();
assert_eq!(invoice_request.chain(), mainnet);
assert_eq!(tlv_stream.chain, None);
@ -1582,7 +1741,7 @@ mod tests {
.chain(Network::Testnet).unwrap()
.build().unwrap()
.sign(payer_sign).unwrap();
let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream();
let (_, _, tlv_stream, _, _, _) = invoice_request.as_tlv_stream();
assert_eq!(invoice_request.chain(), testnet);
assert_eq!(tlv_stream.chain, Some(&testnet));
@ -1595,7 +1754,7 @@ mod tests {
.chain(Network::Bitcoin).unwrap()
.build().unwrap()
.sign(payer_sign).unwrap();
let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream();
let (_, _, tlv_stream, _, _, _) = invoice_request.as_tlv_stream();
assert_eq!(invoice_request.chain(), mainnet);
assert_eq!(tlv_stream.chain, None);
@ -1609,7 +1768,7 @@ mod tests {
.chain(Network::Testnet).unwrap()
.build().unwrap()
.sign(payer_sign).unwrap();
let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream();
let (_, _, tlv_stream, _, _, _) = invoice_request.as_tlv_stream();
assert_eq!(invoice_request.chain(), testnet);
assert_eq!(tlv_stream.chain, Some(&testnet));
@ -1645,7 +1804,7 @@ mod tests {
.amount_msats(1000).unwrap()
.build().unwrap()
.sign(payer_sign).unwrap();
let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream();
let (_, _, tlv_stream, _, _, _) = invoice_request.as_tlv_stream();
assert_eq!(invoice_request.amount_msats(), Some(1000));
assert_eq!(tlv_stream.amount, Some(1000));
@ -1657,7 +1816,7 @@ mod tests {
.amount_msats(1000).unwrap()
.build().unwrap()
.sign(payer_sign).unwrap();
let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream();
let (_, _, tlv_stream, _, _, _) = invoice_request.as_tlv_stream();
assert_eq!(invoice_request.amount_msats(), Some(1000));
assert_eq!(tlv_stream.amount, Some(1000));
@ -1668,7 +1827,7 @@ mod tests {
.amount_msats(1001).unwrap()
.build().unwrap()
.sign(payer_sign).unwrap();
let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream();
let (_, _, tlv_stream, _, _, _) = invoice_request.as_tlv_stream();
assert_eq!(invoice_request.amount_msats(), Some(1001));
assert_eq!(tlv_stream.amount, Some(1001));
@ -1748,7 +1907,7 @@ mod tests {
.features_unchecked(InvoiceRequestFeatures::unknown())
.build().unwrap()
.sign(payer_sign).unwrap();
let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream();
let (_, _, tlv_stream, _, _, _) = invoice_request.as_tlv_stream();
assert_eq!(invoice_request.invoice_request_features(), &InvoiceRequestFeatures::unknown());
assert_eq!(tlv_stream.features, Some(&InvoiceRequestFeatures::unknown()));
@ -1760,7 +1919,7 @@ mod tests {
.features_unchecked(InvoiceRequestFeatures::empty())
.build().unwrap()
.sign(payer_sign).unwrap();
let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream();
let (_, _, tlv_stream, _, _, _) = invoice_request.as_tlv_stream();
assert_eq!(invoice_request.invoice_request_features(), &InvoiceRequestFeatures::empty());
assert_eq!(tlv_stream.features, None);
}
@ -1777,7 +1936,7 @@ mod tests {
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap()
.sign(payer_sign).unwrap();
let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream();
let (_, _, tlv_stream, _, _, _) = invoice_request.as_tlv_stream();
assert_eq!(invoice_request.quantity(), None);
assert_eq!(tlv_stream.quantity, None);
@ -1802,7 +1961,7 @@ mod tests {
.quantity(10).unwrap()
.build().unwrap()
.sign(payer_sign).unwrap();
let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream();
let (_, _, tlv_stream, _, _, _) = invoice_request.as_tlv_stream();
assert_eq!(invoice_request.amount_msats(), Some(10_000));
assert_eq!(tlv_stream.amount, Some(10_000));
@ -1827,7 +1986,7 @@ mod tests {
.quantity(2).unwrap()
.build().unwrap()
.sign(payer_sign).unwrap();
let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream();
let (_, _, tlv_stream, _, _, _) = invoice_request.as_tlv_stream();
assert_eq!(invoice_request.amount_msats(), Some(2_000));
assert_eq!(tlv_stream.amount, Some(2_000));
@ -1863,7 +2022,7 @@ mod tests {
.payer_note("bar".into())
.build().unwrap()
.sign(payer_sign).unwrap();
let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream();
let (_, _, tlv_stream, _, _, _) = invoice_request.as_tlv_stream();
assert_eq!(invoice_request.payer_note(), Some(PrintableString("bar")));
assert_eq!(tlv_stream.payer_note, Some(&String::from("bar")));
@ -1875,7 +2034,7 @@ mod tests {
.payer_note("baz".into())
.build().unwrap()
.sign(payer_sign).unwrap();
let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream();
let (_, _, tlv_stream, _, _, _) = invoice_request.as_tlv_stream();
assert_eq!(invoice_request.payer_note(), Some(PrintableString("baz")));
assert_eq!(tlv_stream.payer_note, Some(&String::from("baz")));
}
@ -2294,9 +2453,196 @@ mod tests {
}
#[test]
fn fails_parsing_invoice_request_with_extra_tlv_records() {
fn parses_invoice_request_with_unknown_tlv_records() {
const UNKNOWN_ODD_TYPE: u64 = INVOICE_REQUEST_TYPES.end - 1;
assert!(UNKNOWN_ODD_TYPE % 2 == 1);
let secp_ctx = Secp256k1::new();
let keys = Keypair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
let mut unsigned_invoice_request = OfferBuilder::new(keys.public_key())
.amount_msats(1000)
.build().unwrap()
.request_invoice(vec![1; 32], keys.public_key()).unwrap()
.build().unwrap();
let mut unknown_bytes = Vec::new();
BigSize(UNKNOWN_ODD_TYPE).write(&mut unknown_bytes).unwrap();
BigSize(32).write(&mut unknown_bytes).unwrap();
[42u8; 32].write(&mut unknown_bytes).unwrap();
unsigned_invoice_request.bytes.reserve_exact(
unsigned_invoice_request.bytes.capacity()
- unsigned_invoice_request.bytes.len()
+ unknown_bytes.len(),
);
unsigned_invoice_request.bytes.extend_from_slice(&unknown_bytes);
unsigned_invoice_request.tagged_hash =
TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &unsigned_invoice_request.bytes);
let invoice_request = unsigned_invoice_request
.sign(|message: &UnsignedInvoiceRequest|
Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
)
.unwrap();
let mut encoded_invoice_request = Vec::new();
invoice_request.write(&mut encoded_invoice_request).unwrap();
match InvoiceRequest::try_from(encoded_invoice_request.clone()) {
Ok(invoice_request) => assert_eq!(invoice_request.bytes, encoded_invoice_request),
Err(e) => panic!("error parsing invoice_request: {:?}", e),
}
const UNKNOWN_EVEN_TYPE: u64 = INVOICE_REQUEST_TYPES.end - 2;
assert!(UNKNOWN_EVEN_TYPE % 2 == 0);
let mut unsigned_invoice_request = OfferBuilder::new(keys.public_key())
.amount_msats(1000)
.build().unwrap()
.request_invoice(vec![1; 32], keys.public_key()).unwrap()
.build().unwrap();
let mut unknown_bytes = Vec::new();
BigSize(UNKNOWN_EVEN_TYPE).write(&mut unknown_bytes).unwrap();
BigSize(32).write(&mut unknown_bytes).unwrap();
[42u8; 32].write(&mut unknown_bytes).unwrap();
unsigned_invoice_request.bytes.reserve_exact(
unsigned_invoice_request.bytes.capacity()
- unsigned_invoice_request.bytes.len()
+ unknown_bytes.len(),
);
unsigned_invoice_request.bytes.extend_from_slice(&unknown_bytes);
unsigned_invoice_request.tagged_hash =
TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &unsigned_invoice_request.bytes);
let invoice_request = unsigned_invoice_request
.sign(|message: &UnsignedInvoiceRequest|
Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
)
.unwrap();
let mut encoded_invoice_request = Vec::new();
invoice_request.write(&mut encoded_invoice_request).unwrap();
match InvoiceRequest::try_from(encoded_invoice_request) {
Ok(_) => panic!("expected error"),
Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::UnknownRequiredFeature)),
}
}
#[test]
fn parses_invoice_request_with_experimental_tlv_records() {
const UNKNOWN_ODD_TYPE: u64 = EXPERIMENTAL_INVOICE_REQUEST_TYPES.start + 1;
assert!(UNKNOWN_ODD_TYPE % 2 == 1);
let secp_ctx = Secp256k1::new();
let keys = Keypair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
let mut unsigned_invoice_request = OfferBuilder::new(keys.public_key())
.amount_msats(1000)
.build().unwrap()
.request_invoice(vec![1; 32], keys.public_key()).unwrap()
.build().unwrap();
let mut unknown_bytes = Vec::new();
BigSize(UNKNOWN_ODD_TYPE).write(&mut unknown_bytes).unwrap();
BigSize(32).write(&mut unknown_bytes).unwrap();
[42u8; 32].write(&mut unknown_bytes).unwrap();
unsigned_invoice_request.bytes.reserve_exact(
unsigned_invoice_request.bytes.capacity()
- unsigned_invoice_request.bytes.len()
+ unknown_bytes.len(),
);
unsigned_invoice_request.experimental_bytes.extend_from_slice(&unknown_bytes);
let tlv_stream = TlvStream::new(&unsigned_invoice_request.bytes)
.chain(TlvStream::new(&unsigned_invoice_request.experimental_bytes));
unsigned_invoice_request.tagged_hash =
TaggedHash::from_tlv_stream(SIGNATURE_TAG, tlv_stream);
let invoice_request = unsigned_invoice_request
.sign(|message: &UnsignedInvoiceRequest|
Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
)
.unwrap();
let mut encoded_invoice_request = Vec::new();
invoice_request.write(&mut encoded_invoice_request).unwrap();
match InvoiceRequest::try_from(encoded_invoice_request.clone()) {
Ok(invoice_request) => assert_eq!(invoice_request.bytes, encoded_invoice_request),
Err(e) => panic!("error parsing invoice_request: {:?}", e),
}
const UNKNOWN_EVEN_TYPE: u64 = EXPERIMENTAL_INVOICE_REQUEST_TYPES.start;
assert!(UNKNOWN_EVEN_TYPE % 2 == 0);
let mut unsigned_invoice_request = OfferBuilder::new(keys.public_key())
.amount_msats(1000)
.build().unwrap()
.request_invoice(vec![1; 32], keys.public_key()).unwrap()
.build().unwrap();
let mut unknown_bytes = Vec::new();
BigSize(UNKNOWN_EVEN_TYPE).write(&mut unknown_bytes).unwrap();
BigSize(32).write(&mut unknown_bytes).unwrap();
[42u8; 32].write(&mut unknown_bytes).unwrap();
unsigned_invoice_request.bytes.reserve_exact(
unsigned_invoice_request.bytes.capacity()
- unsigned_invoice_request.bytes.len()
+ unknown_bytes.len(),
);
unsigned_invoice_request.experimental_bytes.extend_from_slice(&unknown_bytes);
let tlv_stream = TlvStream::new(&unsigned_invoice_request.bytes)
.chain(TlvStream::new(&unsigned_invoice_request.experimental_bytes));
unsigned_invoice_request.tagged_hash =
TaggedHash::from_tlv_stream(SIGNATURE_TAG, tlv_stream);
let invoice_request = unsigned_invoice_request
.sign(|message: &UnsignedInvoiceRequest|
Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
)
.unwrap();
let mut encoded_invoice_request = Vec::new();
invoice_request.write(&mut encoded_invoice_request).unwrap();
match InvoiceRequest::try_from(encoded_invoice_request) {
Ok(_) => panic!("expected error"),
Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::UnknownRequiredFeature)),
}
let invoice_request = OfferBuilder::new(keys.public_key())
.amount_msats(1000)
.build().unwrap()
.request_invoice(vec![1; 32], keys.public_key()).unwrap()
.build().unwrap()
.sign(|message: &UnsignedInvoiceRequest|
Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
)
.unwrap();
let mut encoded_invoice_request = Vec::new();
invoice_request.write(&mut encoded_invoice_request).unwrap();
BigSize(UNKNOWN_ODD_TYPE).write(&mut encoded_invoice_request).unwrap();
BigSize(32).write(&mut encoded_invoice_request).unwrap();
[42u8; 32].write(&mut encoded_invoice_request).unwrap();
match InvoiceRequest::try_from(encoded_invoice_request) {
Ok(_) => panic!("expected error"),
Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSignature(secp256k1::Error::IncorrectSignature)),
}
}
#[test]
fn fails_parsing_invoice_request_with_out_of_range_tlv_records() {
let secp_ctx = Secp256k1::new();
let keys = Keypair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
let invoice_request = OfferBuilder::new(keys.public_key())
.amount_msats(1000)
.build().unwrap()
@ -2317,6 +2663,17 @@ mod tests {
Ok(_) => panic!("expected error"),
Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::InvalidValue)),
}
let mut encoded_invoice_request = Vec::new();
invoice_request.write(&mut encoded_invoice_request).unwrap();
BigSize(EXPERIMENTAL_INVOICE_REQUEST_TYPES.end).write(&mut encoded_invoice_request).unwrap();
BigSize(32).write(&mut encoded_invoice_request).unwrap();
[42u8; 32].write(&mut encoded_invoice_request).unwrap();
match InvoiceRequest::try_from(encoded_invoice_request) {
Ok(_) => panic!("expected error"),
Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::InvalidValue)),
}
}
#[test]

View File

@ -11,6 +11,7 @@
use bitcoin::hashes::{Hash, HashEngine, sha256};
use bitcoin::secp256k1::{Message, PublicKey, Secp256k1, self};
use bitcoin::secp256k1::constants::SCHNORR_SIGNATURE_SIZE;
use bitcoin::secp256k1::schnorr::Signature;
use crate::io;
use crate::util::ser::{BigSize, Readable, Writeable, Writer};
@ -19,12 +20,16 @@ use crate::util::ser::{BigSize, Readable, Writeable, Writer};
use crate::prelude::*;
/// Valid type range for signature TLV records.
const SIGNATURE_TYPES: core::ops::RangeInclusive<u64> = 240..=1000;
pub(super) const SIGNATURE_TYPES: core::ops::RangeInclusive<u64> = 240..=1000;
tlv_stream!(SignatureTlvStream, SignatureTlvStreamRef, SIGNATURE_TYPES, {
tlv_stream!(SignatureTlvStream, SignatureTlvStreamRef<'a>, SIGNATURE_TYPES, {
(240, signature: Signature),
});
/// Size of a TLV record in `SIGNATURE_TYPES` when the type is 1000. TLV types are encoded using
/// BigSize, so a TLV record with type 240 will use two less bytes.
pub(super) const SIGNATURE_TLV_RECORD_SIZE: usize = 3 + 1 + SCHNORR_SIGNATURE_SIZE;
/// A hash for use in a specific context by tweaking with a context-dependent tag as per [BIP 340]
/// and computed over the merkle root of a TLV stream to sign as defined in [BOLT 12].
///
@ -164,7 +169,7 @@ fn root_hash<'a, I: core::iter::Iterator<Item = TlvRecord<'a>>>(tlv_stream: I) -
let branch_tag = tagged_hash_engine(sha256::Hash::hash("LnBranch".as_bytes()));
let mut leaves = Vec::new();
for record in TlvStream::skip_signatures(tlv_stream) {
for record in tlv_stream.filter(|record| !SIGNATURE_TYPES.contains(&record.r#type)) {
leaves.push(tagged_hash_from_engine(leaf_tag.clone(), &record.record_bytes));
leaves.push(tagged_hash_from_engine(nonce_tag.clone(), &record.type_bytes));
}
@ -240,21 +245,16 @@ impl<'a> TlvStream<'a> {
self.skip_while(move |record| !types.contains(&record.r#type))
.take_while(move |record| take_range.contains(&record.r#type))
}
fn skip_signatures(
tlv_stream: impl core::iter::Iterator<Item = TlvRecord<'a>>
) -> impl core::iter::Iterator<Item = TlvRecord<'a>> {
tlv_stream.filter(|record| !SIGNATURE_TYPES.contains(&record.r#type))
}
}
/// A slice into a [`TlvStream`] for a record.
#[derive(Eq, PartialEq)]
pub(super) struct TlvRecord<'a> {
pub(super) r#type: u64,
type_bytes: &'a [u8],
// The entire TLV record.
pub(super) record_bytes: &'a [u8],
pub(super) start: usize,
pub(super) end: usize,
}
impl<'a> Iterator for TlvStream<'a> {
@ -277,32 +277,25 @@ impl<'a> Iterator for TlvStream<'a> {
self.data.set_position(end);
Some(TlvRecord { r#type, type_bytes, record_bytes })
Some(TlvRecord {
r#type, type_bytes, record_bytes, start: start as usize, end: end as usize,
})
} else {
None
}
}
}
/// Encoding for a pre-serialized TLV stream that excludes any signature TLV records.
///
/// Panics if the wrapped bytes are not a well-formed TLV stream.
pub(super) struct WithoutSignatures<'a>(pub &'a [u8]);
impl<'a> Writeable for WithoutSignatures<'a> {
impl<'a> Writeable for TlvRecord<'a> {
#[inline]
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
let tlv_stream = TlvStream::new(self.0);
for record in TlvStream::skip_signatures(tlv_stream) {
writer.write_all(record.record_bytes)?;
}
Ok(())
writer.write_all(self.record_bytes)
}
}
#[cfg(test)]
mod tests {
use super::{SIGNATURE_TYPES, TlvStream, WithoutSignatures};
use super::{SIGNATURE_TYPES, TlvStream};
use bitcoin::hashes::{Hash, sha256};
use bitcoin::hex::FromHex;
@ -412,7 +405,11 @@ mod tests {
.unwrap();
let mut bytes_without_signature = Vec::new();
WithoutSignatures(&invoice_request.bytes).write(&mut bytes_without_signature).unwrap();
let tlv_stream_without_signatures = TlvStream::new(&invoice_request.bytes)
.filter(|record| !SIGNATURE_TYPES.contains(&record.r#type));
for record in tlv_stream_without_signatures {
record.write(&mut bytes_without_signature).unwrap();
}
assert_ne!(bytes_without_signature, invoice_request.bytes);
assert_eq!(

View File

@ -91,11 +91,11 @@ use crate::ln::channelmanager::PaymentId;
use crate::types::features::OfferFeatures;
use crate::ln::inbound_payment::{ExpandedKey, IV_LEN};
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
use crate::offers::merkle::{TaggedHash, TlvStream};
use crate::offers::merkle::{TaggedHash, TlvRecord, TlvStream};
use crate::offers::nonce::Nonce;
use crate::offers::parse::{Bech32Encode, Bolt12ParseError, Bolt12SemanticError, ParsedMessage};
use crate::offers::signer::{Metadata, MetadataMaterial, self};
use crate::util::ser::{HighZeroBytesDroppedBigSize, Readable, WithoutLength, Writeable, Writer};
use crate::util::ser::{CursorReadable, HighZeroBytesDroppedBigSize, Readable, WithoutLength, Writeable, Writer};
use crate::util::string::PrintableString;
#[cfg(not(c_bindings))]
@ -130,7 +130,7 @@ impl OfferId {
}
fn from_valid_invreq_tlv_stream(bytes: &[u8]) -> Self {
let tlv_stream = TlvStream::new(bytes).range(OFFER_TYPES);
let tlv_stream = Offer::tlv_stream_iter(bytes);
let tagged_hash = TaggedHash::from_tlv_stream(Self::ID_TAG, tlv_stream);
Self(tagged_hash.to_bytes())
}
@ -239,6 +239,8 @@ macro_rules! offer_explicit_metadata_builder_methods { (
chains: None, metadata: None, amount: None, description: None,
features: OfferFeatures::empty(), absolute_expiry: None, issuer: None, paths: None,
supported_quantity: Quantity::One, issuer_signing_pubkey: Some(signing_pubkey),
#[cfg(test)]
experimental_foo: None,
},
metadata_strategy: core::marker::PhantomData,
secp_ctx: None,
@ -280,6 +282,8 @@ macro_rules! offer_derived_metadata_builder_methods { ($secp_context: ty) => {
chains: None, metadata: Some(metadata), amount: None, description: None,
features: OfferFeatures::empty(), absolute_expiry: None, issuer: None, paths: None,
supported_quantity: Quantity::One, issuer_signing_pubkey: Some(node_id),
#[cfg(test)]
experimental_foo: None,
},
metadata_strategy: core::marker::PhantomData,
secp_ctx: Some(secp_ctx),
@ -414,10 +418,10 @@ macro_rules! offer_builder_methods { (
};
let mut tlv_stream = $self.offer.as_tlv_stream();
debug_assert_eq!(tlv_stream.metadata, None);
tlv_stream.metadata = None;
debug_assert_eq!(tlv_stream.0.metadata, None);
tlv_stream.0.metadata = None;
if metadata.derives_recipient_keys() {
tlv_stream.issuer_id = None;
tlv_stream.0.issuer_id = None;
}
// Either replace the signing pubkey with the derived pubkey or include the metadata
@ -478,6 +482,12 @@ macro_rules! offer_builder_test_methods { (
$return_value
}
#[cfg_attr(c_bindings, allow(dead_code))]
pub(super) fn experimental_foo($($self_mut)* $self: $self_type, experimental_foo: u64) -> $return_type {
$self.offer.experimental_foo = Some(experimental_foo);
$return_value
}
#[cfg_attr(c_bindings, allow(dead_code))]
pub(super) fn build_unchecked($self: $self_type) -> Offer {
$self.build_without_checks()
@ -585,6 +595,8 @@ pub(super) struct OfferContents {
paths: Option<Vec<BlindedMessagePath>>,
supported_quantity: Quantity,
issuer_signing_pubkey: Option<PublicKey>,
#[cfg(test)]
experimental_foo: Option<u64>,
}
macro_rules! offer_accessors { ($self: ident, $contents: expr) => {
@ -701,6 +713,13 @@ impl Offer {
self.contents.expects_quantity()
}
pub(super) fn tlv_stream_iter<'a>(
bytes: &'a [u8]
) -> impl core::iter::Iterator<Item = TlvRecord<'a>> {
TlvStream::new(bytes).range(OFFER_TYPES)
.chain(TlvStream::new(bytes).range(EXPERIMENTAL_OFFER_TYPES))
}
#[cfg(async_payments)]
pub(super) fn verify<T: secp256k1::Signing>(
&self, nonce: Nonce, key: &ExpandedKey, secp_ctx: &Secp256k1<T>
@ -803,7 +822,7 @@ impl Offer {
#[cfg(test)]
impl Offer {
pub(super) fn as_tlv_stream(&self) -> OfferTlvStreamRef {
pub(super) fn as_tlv_stream(&self) -> FullOfferTlvStreamRef {
self.contents.as_tlv_stream()
}
}
@ -971,7 +990,9 @@ impl OfferContents {
OFFER_ISSUER_ID_TYPE => !metadata.derives_recipient_keys(),
_ => true,
}
});
})
.chain(TlvStream::new(bytes).range(EXPERIMENTAL_OFFER_TYPES));
let signing_pubkey = match self.issuer_signing_pubkey() {
Some(signing_pubkey) => signing_pubkey,
None => return Err(()),
@ -988,7 +1009,7 @@ impl OfferContents {
}
}
pub(super) fn as_tlv_stream(&self) -> OfferTlvStreamRef {
pub(super) fn as_tlv_stream(&self) -> FullOfferTlvStreamRef {
let (currency, amount) = match &self.amount {
None => (None, None),
Some(Amount::Bitcoin { amount_msats }) => (None, Some(*amount_msats)),
@ -1001,7 +1022,7 @@ impl OfferContents {
if self.features == OfferFeatures::empty() { None } else { Some(&self.features) }
};
OfferTlvStreamRef {
let offer = OfferTlvStreamRef {
chains: self.chains.as_ref(),
metadata: self.metadata(),
currency,
@ -1013,7 +1034,14 @@ impl OfferContents {
issuer: self.issuer.as_ref(),
quantity_max: self.supported_quantity.to_tlv_record(),
issuer_id: self.issuer_signing_pubkey.as_ref(),
}
};
let experimental_offer = ExperimentalOfferTlvStreamRef {
#[cfg(test)]
experimental_foo: self.experimental_foo,
};
(offer, experimental_offer)
}
}
@ -1091,7 +1119,7 @@ const OFFER_METADATA_TYPE: u64 = 4;
/// TLV record type for [`Offer::issuer_signing_pubkey`].
const OFFER_ISSUER_ID_TYPE: u64 = 22;
tlv_stream!(OfferTlvStream, OfferTlvStreamRef, OFFER_TYPES, {
tlv_stream!(OfferTlvStream, OfferTlvStreamRef<'a>, OFFER_TYPES, {
(2, chains: (Vec<ChainHash>, WithoutLength)),
(OFFER_METADATA_TYPE, metadata: (Vec<u8>, WithoutLength)),
(6, currency: CurrencyCode),
@ -1105,6 +1133,31 @@ tlv_stream!(OfferTlvStream, OfferTlvStreamRef, OFFER_TYPES, {
(OFFER_ISSUER_ID_TYPE, issuer_id: PublicKey),
});
/// Valid type range for experimental offer TLV records.
pub(super) const EXPERIMENTAL_OFFER_TYPES: core::ops::Range<u64> = 1_000_000_000..2_000_000_000;
#[cfg(not(test))]
tlv_stream!(ExperimentalOfferTlvStream, ExperimentalOfferTlvStreamRef, EXPERIMENTAL_OFFER_TYPES, {
});
#[cfg(test)]
tlv_stream!(ExperimentalOfferTlvStream, ExperimentalOfferTlvStreamRef, EXPERIMENTAL_OFFER_TYPES, {
(1_999_999_999, experimental_foo: (u64, HighZeroBytesDroppedBigSize)),
});
type FullOfferTlvStream = (OfferTlvStream, ExperimentalOfferTlvStream);
type FullOfferTlvStreamRef<'a> = (OfferTlvStreamRef<'a>, ExperimentalOfferTlvStreamRef);
impl CursorReadable for FullOfferTlvStream {
fn read<R: AsRef<[u8]>>(r: &mut io::Cursor<R>) -> Result<Self, DecodeError> {
let offer = CursorReadable::read(r)?;
let experimental_offer = CursorReadable::read(r)?;
Ok((offer, experimental_offer))
}
}
impl Bech32Encode for Offer {
const BECH32_HRP: &'static str = "lno";
}
@ -1121,7 +1174,7 @@ impl TryFrom<Vec<u8>> for Offer {
type Error = Bolt12ParseError;
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
let offer = ParsedMessage::<OfferTlvStream>::try_from(bytes)?;
let offer = ParsedMessage::<FullOfferTlvStream>::try_from(bytes)?;
let ParsedMessage { bytes, tlv_stream } = offer;
let contents = OfferContents::try_from(tlv_stream)?;
let id = OfferId::from_valid_offer_tlv_stream(&bytes);
@ -1130,14 +1183,20 @@ impl TryFrom<Vec<u8>> for Offer {
}
}
impl TryFrom<OfferTlvStream> for OfferContents {
impl TryFrom<FullOfferTlvStream> for OfferContents {
type Error = Bolt12SemanticError;
fn try_from(tlv_stream: OfferTlvStream) -> Result<Self, Self::Error> {
let OfferTlvStream {
chains, metadata, currency, amount, description, features, absolute_expiry, paths,
issuer, quantity_max, issuer_id,
} = tlv_stream;
fn try_from(tlv_stream: FullOfferTlvStream) -> Result<Self, Self::Error> {
let (
OfferTlvStream {
chains, metadata, currency, amount, description, features, absolute_expiry, paths,
issuer, quantity_max, issuer_id,
},
ExperimentalOfferTlvStream {
#[cfg(test)]
experimental_foo,
},
) = tlv_stream;
let metadata = metadata.map(|metadata| Metadata::Bytes(metadata));
@ -1175,6 +1234,8 @@ impl TryFrom<OfferTlvStream> for OfferContents {
Ok(OfferContents {
chains, metadata, amount, description, features, absolute_expiry, issuer, paths,
supported_quantity, issuer_signing_pubkey,
#[cfg(test)]
experimental_foo,
})
}
}
@ -1187,7 +1248,7 @@ impl core::fmt::Display for Offer {
#[cfg(test)]
mod tests {
use super::{Amount, Offer, OfferTlvStreamRef, Quantity};
use super::{Amount, EXPERIMENTAL_OFFER_TYPES, ExperimentalOfferTlvStreamRef, OFFER_TYPES, Offer, OfferTlvStreamRef, Quantity};
#[cfg(not(c_bindings))]
use {
super::OfferBuilder,
@ -1239,19 +1300,24 @@ mod tests {
assert_eq!(
offer.as_tlv_stream(),
OfferTlvStreamRef {
chains: None,
metadata: None,
currency: None,
amount: None,
description: None,
features: None,
absolute_expiry: None,
paths: None,
issuer: None,
quantity_max: None,
issuer_id: Some(&pubkey(42)),
},
(
OfferTlvStreamRef {
chains: None,
metadata: None,
currency: None,
amount: None,
description: None,
features: None,
absolute_expiry: None,
paths: None,
issuer: None,
quantity_max: None,
issuer_id: Some(&pubkey(42)),
},
ExperimentalOfferTlvStreamRef {
experimental_foo: None,
},
),
);
if let Err(e) = Offer::try_from(buffer) {
@ -1270,7 +1336,7 @@ mod tests {
.unwrap();
assert!(offer.supports_chain(mainnet));
assert_eq!(offer.chains(), vec![mainnet]);
assert_eq!(offer.as_tlv_stream().chains, None);
assert_eq!(offer.as_tlv_stream().0.chains, None);
let offer = OfferBuilder::new(pubkey(42))
.chain(Network::Testnet)
@ -1278,7 +1344,7 @@ mod tests {
.unwrap();
assert!(offer.supports_chain(testnet));
assert_eq!(offer.chains(), vec![testnet]);
assert_eq!(offer.as_tlv_stream().chains, Some(&vec![testnet]));
assert_eq!(offer.as_tlv_stream().0.chains, Some(&vec![testnet]));
let offer = OfferBuilder::new(pubkey(42))
.chain(Network::Testnet)
@ -1287,7 +1353,7 @@ mod tests {
.unwrap();
assert!(offer.supports_chain(testnet));
assert_eq!(offer.chains(), vec![testnet]);
assert_eq!(offer.as_tlv_stream().chains, Some(&vec![testnet]));
assert_eq!(offer.as_tlv_stream().0.chains, Some(&vec![testnet]));
let offer = OfferBuilder::new(pubkey(42))
.chain(Network::Bitcoin)
@ -1297,7 +1363,7 @@ mod tests {
assert!(offer.supports_chain(mainnet));
assert!(offer.supports_chain(testnet));
assert_eq!(offer.chains(), vec![mainnet, testnet]);
assert_eq!(offer.as_tlv_stream().chains, Some(&vec![mainnet, testnet]));
assert_eq!(offer.as_tlv_stream().0.chains, Some(&vec![mainnet, testnet]));
}
#[test]
@ -1307,7 +1373,7 @@ mod tests {
.build()
.unwrap();
assert_eq!(offer.metadata(), Some(&vec![42; 32]));
assert_eq!(offer.as_tlv_stream().metadata, Some(&vec![42; 32]));
assert_eq!(offer.as_tlv_stream().0.metadata, Some(&vec![42; 32]));
let offer = OfferBuilder::new(pubkey(42))
.metadata(vec![42; 32]).unwrap()
@ -1315,7 +1381,7 @@ mod tests {
.build()
.unwrap();
assert_eq!(offer.metadata(), Some(&vec![43; 32]));
assert_eq!(offer.as_tlv_stream().metadata, Some(&vec![43; 32]));
assert_eq!(offer.as_tlv_stream().0.metadata, Some(&vec![43; 32]));
}
#[test]
@ -1330,6 +1396,7 @@ mod tests {
use super::OfferWithDerivedMetadataBuilder as OfferBuilder;
let offer = OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, nonce, &secp_ctx)
.amount_msats(1000)
.experimental_foo(42)
.build().unwrap();
assert!(offer.metadata().is_some());
assert_eq!(offer.issuer_signing_pubkey(), Some(node_id));
@ -1352,7 +1419,7 @@ mod tests {
// Fails verification with altered offer field
let mut tlv_stream = offer.as_tlv_stream();
tlv_stream.amount = Some(100);
tlv_stream.0.amount = Some(100);
let mut encoded_offer = Vec::new();
tlv_stream.write(&mut encoded_offer).unwrap();
@ -1365,8 +1432,8 @@ mod tests {
// Fails verification with altered metadata
let mut tlv_stream = offer.as_tlv_stream();
let metadata = tlv_stream.metadata.unwrap().iter().copied().rev().collect();
tlv_stream.metadata = Some(&metadata);
let metadata = tlv_stream.0.metadata.unwrap().iter().copied().rev().collect();
tlv_stream.0.metadata = Some(&metadata);
let mut encoded_offer = Vec::new();
tlv_stream.write(&mut encoded_offer).unwrap();
@ -1399,6 +1466,7 @@ mod tests {
let offer = OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, nonce, &secp_ctx)
.amount_msats(1000)
.path(blinded_path)
.experimental_foo(42)
.build().unwrap();
assert!(offer.metadata().is_none());
assert_ne!(offer.issuer_signing_pubkey(), Some(node_id));
@ -1419,7 +1487,7 @@ mod tests {
// Fails verification with altered offer field
let mut tlv_stream = offer.as_tlv_stream();
tlv_stream.amount = Some(100);
tlv_stream.0.amount = Some(100);
let mut encoded_offer = Vec::new();
tlv_stream.write(&mut encoded_offer).unwrap();
@ -1435,7 +1503,7 @@ mod tests {
// Fails verification with altered signing pubkey
let mut tlv_stream = offer.as_tlv_stream();
let issuer_id = pubkey(1);
tlv_stream.issuer_id = Some(&issuer_id);
tlv_stream.0.issuer_id = Some(&issuer_id);
let mut encoded_offer = Vec::new();
tlv_stream.write(&mut encoded_offer).unwrap();
@ -1460,8 +1528,8 @@ mod tests {
.unwrap();
let tlv_stream = offer.as_tlv_stream();
assert_eq!(offer.amount(), Some(bitcoin_amount));
assert_eq!(tlv_stream.amount, Some(1000));
assert_eq!(tlv_stream.currency, None);
assert_eq!(tlv_stream.0.amount, Some(1000));
assert_eq!(tlv_stream.0.currency, None);
#[cfg(not(c_bindings))]
let builder = OfferBuilder::new(pubkey(42))
@ -1472,8 +1540,8 @@ mod tests {
builder.amount(currency_amount.clone());
let tlv_stream = builder.offer.as_tlv_stream();
assert_eq!(builder.offer.amount, Some(currency_amount.clone()));
assert_eq!(tlv_stream.amount, Some(10));
assert_eq!(tlv_stream.currency, Some(b"USD"));
assert_eq!(tlv_stream.0.amount, Some(10));
assert_eq!(tlv_stream.0.currency, Some(b"USD"));
match builder.build() {
Ok(_) => panic!("expected error"),
Err(e) => assert_eq!(e, Bolt12SemanticError::UnsupportedCurrency),
@ -1485,8 +1553,8 @@ mod tests {
.build()
.unwrap();
let tlv_stream = offer.as_tlv_stream();
assert_eq!(tlv_stream.amount, Some(1000));
assert_eq!(tlv_stream.currency, None);
assert_eq!(tlv_stream.0.amount, Some(1000));
assert_eq!(tlv_stream.0.currency, None);
let invalid_amount = Amount::Bitcoin { amount_msats: MAX_VALUE_MSAT + 1 };
match OfferBuilder::new(pubkey(42)).amount(invalid_amount).build() {
@ -1502,7 +1570,7 @@ mod tests {
.build()
.unwrap();
assert_eq!(offer.description(), Some(PrintableString("foo")));
assert_eq!(offer.as_tlv_stream().description, Some(&String::from("foo")));
assert_eq!(offer.as_tlv_stream().0.description, Some(&String::from("foo")));
let offer = OfferBuilder::new(pubkey(42))
.description("foo".into())
@ -1510,14 +1578,14 @@ mod tests {
.build()
.unwrap();
assert_eq!(offer.description(), Some(PrintableString("bar")));
assert_eq!(offer.as_tlv_stream().description, Some(&String::from("bar")));
assert_eq!(offer.as_tlv_stream().0.description, Some(&String::from("bar")));
let offer = OfferBuilder::new(pubkey(42))
.amount_msats(1000)
.build()
.unwrap();
assert_eq!(offer.description(), Some(PrintableString("")));
assert_eq!(offer.as_tlv_stream().description, Some(&String::from("")));
assert_eq!(offer.as_tlv_stream().0.description, Some(&String::from("")));
}
#[test]
@ -1527,7 +1595,7 @@ mod tests {
.build()
.unwrap();
assert_eq!(offer.offer_features(), &OfferFeatures::unknown());
assert_eq!(offer.as_tlv_stream().features, Some(&OfferFeatures::unknown()));
assert_eq!(offer.as_tlv_stream().0.features, Some(&OfferFeatures::unknown()));
let offer = OfferBuilder::new(pubkey(42))
.features_unchecked(OfferFeatures::unknown())
@ -1535,7 +1603,7 @@ mod tests {
.build()
.unwrap();
assert_eq!(offer.offer_features(), &OfferFeatures::empty());
assert_eq!(offer.as_tlv_stream().features, None);
assert_eq!(offer.as_tlv_stream().0.features, None);
}
#[test]
@ -1552,7 +1620,7 @@ mod tests {
assert!(!offer.is_expired());
assert!(!offer.is_expired_no_std(now));
assert_eq!(offer.absolute_expiry(), Some(future_expiry));
assert_eq!(offer.as_tlv_stream().absolute_expiry, Some(future_expiry.as_secs()));
assert_eq!(offer.as_tlv_stream().0.absolute_expiry, Some(future_expiry.as_secs()));
let offer = OfferBuilder::new(pubkey(42))
.absolute_expiry(future_expiry)
@ -1563,7 +1631,7 @@ mod tests {
assert!(offer.is_expired());
assert!(offer.is_expired_no_std(now));
assert_eq!(offer.absolute_expiry(), Some(past_expiry));
assert_eq!(offer.as_tlv_stream().absolute_expiry, Some(past_expiry.as_secs()));
assert_eq!(offer.as_tlv_stream().0.absolute_expiry, Some(past_expiry.as_secs()));
}
#[test]
@ -1594,8 +1662,8 @@ mod tests {
assert_eq!(offer.paths(), paths.as_slice());
assert_eq!(offer.issuer_signing_pubkey(), Some(pubkey(42)));
assert_ne!(pubkey(42), pubkey(44));
assert_eq!(tlv_stream.paths, Some(&paths));
assert_eq!(tlv_stream.issuer_id, Some(&pubkey(42)));
assert_eq!(tlv_stream.0.paths, Some(&paths));
assert_eq!(tlv_stream.0.issuer_id, Some(&pubkey(42)));
}
#[test]
@ -1605,7 +1673,7 @@ mod tests {
.build()
.unwrap();
assert_eq!(offer.issuer(), Some(PrintableString("foo")));
assert_eq!(offer.as_tlv_stream().issuer, Some(&String::from("foo")));
assert_eq!(offer.as_tlv_stream().0.issuer, Some(&String::from("foo")));
let offer = OfferBuilder::new(pubkey(42))
.issuer("foo".into())
@ -1613,7 +1681,7 @@ mod tests {
.build()
.unwrap();
assert_eq!(offer.issuer(), Some(PrintableString("bar")));
assert_eq!(offer.as_tlv_stream().issuer, Some(&String::from("bar")));
assert_eq!(offer.as_tlv_stream().0.issuer, Some(&String::from("bar")));
}
#[test]
@ -1628,7 +1696,7 @@ mod tests {
let tlv_stream = offer.as_tlv_stream();
assert!(!offer.expects_quantity());
assert_eq!(offer.supported_quantity(), Quantity::One);
assert_eq!(tlv_stream.quantity_max, None);
assert_eq!(tlv_stream.0.quantity_max, None);
let offer = OfferBuilder::new(pubkey(42))
.supported_quantity(Quantity::Unbounded)
@ -1637,7 +1705,7 @@ mod tests {
let tlv_stream = offer.as_tlv_stream();
assert!(offer.expects_quantity());
assert_eq!(offer.supported_quantity(), Quantity::Unbounded);
assert_eq!(tlv_stream.quantity_max, Some(0));
assert_eq!(tlv_stream.0.quantity_max, Some(0));
let offer = OfferBuilder::new(pubkey(42))
.supported_quantity(Quantity::Bounded(ten))
@ -1646,7 +1714,7 @@ mod tests {
let tlv_stream = offer.as_tlv_stream();
assert!(offer.expects_quantity());
assert_eq!(offer.supported_quantity(), Quantity::Bounded(ten));
assert_eq!(tlv_stream.quantity_max, Some(10));
assert_eq!(tlv_stream.0.quantity_max, Some(10));
let offer = OfferBuilder::new(pubkey(42))
.supported_quantity(Quantity::Bounded(one))
@ -1655,7 +1723,7 @@ mod tests {
let tlv_stream = offer.as_tlv_stream();
assert!(offer.expects_quantity());
assert_eq!(offer.supported_quantity(), Quantity::Bounded(one));
assert_eq!(tlv_stream.quantity_max, Some(1));
assert_eq!(tlv_stream.0.quantity_max, Some(1));
let offer = OfferBuilder::new(pubkey(42))
.supported_quantity(Quantity::Bounded(ten))
@ -1665,7 +1733,7 @@ mod tests {
let tlv_stream = offer.as_tlv_stream();
assert!(!offer.expects_quantity());
assert_eq!(offer.supported_quantity(), Quantity::One);
assert_eq!(tlv_stream.quantity_max, None);
assert_eq!(tlv_stream.0.quantity_max, None);
}
#[test]
@ -1703,8 +1771,8 @@ mod tests {
}
let mut tlv_stream = offer.as_tlv_stream();
tlv_stream.amount = Some(1000);
tlv_stream.currency = Some(b"USD");
tlv_stream.0.amount = Some(1000);
tlv_stream.0.currency = Some(b"USD");
let mut encoded_offer = Vec::new();
tlv_stream.write(&mut encoded_offer).unwrap();
@ -1714,8 +1782,8 @@ mod tests {
}
let mut tlv_stream = offer.as_tlv_stream();
tlv_stream.amount = None;
tlv_stream.currency = Some(b"USD");
tlv_stream.0.amount = None;
tlv_stream.0.currency = Some(b"USD");
let mut encoded_offer = Vec::new();
tlv_stream.write(&mut encoded_offer).unwrap();
@ -1726,8 +1794,8 @@ mod tests {
}
let mut tlv_stream = offer.as_tlv_stream();
tlv_stream.amount = Some(MAX_VALUE_MSAT + 1);
tlv_stream.currency = None;
tlv_stream.0.amount = Some(MAX_VALUE_MSAT + 1);
tlv_stream.0.currency = None;
let mut encoded_offer = Vec::new();
tlv_stream.write(&mut encoded_offer).unwrap();
@ -1754,7 +1822,7 @@ mod tests {
}
let mut tlv_stream = offer.as_tlv_stream();
tlv_stream.description = None;
tlv_stream.0.description = None;
let mut encoded_offer = Vec::new();
tlv_stream.write(&mut encoded_offer).unwrap();
@ -1860,7 +1928,7 @@ mod tests {
}
let mut tlv_stream = offer.as_tlv_stream();
tlv_stream.issuer_id = None;
tlv_stream.0.issuer_id = None;
let mut encoded_offer = Vec::new();
tlv_stream.write(&mut encoded_offer).unwrap();
@ -1874,12 +1942,89 @@ mod tests {
}
#[test]
fn fails_parsing_offer_with_extra_tlv_records() {
fn parses_offer_with_unknown_tlv_records() {
const UNKNOWN_ODD_TYPE: u64 = OFFER_TYPES.end - 1;
assert!(UNKNOWN_ODD_TYPE % 2 == 1);
let offer = OfferBuilder::new(pubkey(42)).build().unwrap();
let mut encoded_offer = Vec::new();
offer.write(&mut encoded_offer).unwrap();
BigSize(80).write(&mut encoded_offer).unwrap();
BigSize(UNKNOWN_ODD_TYPE).write(&mut encoded_offer).unwrap();
BigSize(32).write(&mut encoded_offer).unwrap();
[42u8; 32].write(&mut encoded_offer).unwrap();
match Offer::try_from(encoded_offer.clone()) {
Ok(offer) => assert_eq!(offer.bytes, encoded_offer),
Err(e) => panic!("error parsing offer: {:?}", e),
}
const UNKNOWN_EVEN_TYPE: u64 = OFFER_TYPES.end - 2;
assert!(UNKNOWN_EVEN_TYPE % 2 == 0);
let offer = OfferBuilder::new(pubkey(42)).build().unwrap();
let mut encoded_offer = Vec::new();
offer.write(&mut encoded_offer).unwrap();
BigSize(UNKNOWN_EVEN_TYPE).write(&mut encoded_offer).unwrap();
BigSize(32).write(&mut encoded_offer).unwrap();
[42u8; 32].write(&mut encoded_offer).unwrap();
match Offer::try_from(encoded_offer) {
Ok(_) => panic!("expected error"),
Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::UnknownRequiredFeature)),
}
}
#[test]
fn parses_offer_with_experimental_tlv_records() {
let offer = OfferBuilder::new(pubkey(42)).build().unwrap();
let mut encoded_offer = Vec::new();
offer.write(&mut encoded_offer).unwrap();
BigSize(EXPERIMENTAL_OFFER_TYPES.start + 1).write(&mut encoded_offer).unwrap();
BigSize(32).write(&mut encoded_offer).unwrap();
[42u8; 32].write(&mut encoded_offer).unwrap();
match Offer::try_from(encoded_offer.clone()) {
Ok(offer) => assert_eq!(offer.bytes, encoded_offer),
Err(e) => panic!("error parsing offer: {:?}", e),
}
let offer = OfferBuilder::new(pubkey(42)).build().unwrap();
let mut encoded_offer = Vec::new();
offer.write(&mut encoded_offer).unwrap();
BigSize(EXPERIMENTAL_OFFER_TYPES.start).write(&mut encoded_offer).unwrap();
BigSize(32).write(&mut encoded_offer).unwrap();
[42u8; 32].write(&mut encoded_offer).unwrap();
match Offer::try_from(encoded_offer) {
Ok(_) => panic!("expected error"),
Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::UnknownRequiredFeature)),
}
}
#[test]
fn fails_parsing_offer_with_out_of_range_tlv_records() {
let offer = OfferBuilder::new(pubkey(42)).build().unwrap();
let mut encoded_offer = Vec::new();
offer.write(&mut encoded_offer).unwrap();
BigSize(OFFER_TYPES.end).write(&mut encoded_offer).unwrap();
BigSize(32).write(&mut encoded_offer).unwrap();
[42u8; 32].write(&mut encoded_offer).unwrap();
match Offer::try_from(encoded_offer) {
Ok(_) => panic!("expected error"),
Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::InvalidValue)),
}
let offer = OfferBuilder::new(pubkey(42)).build().unwrap();
let mut encoded_offer = Vec::new();
offer.write(&mut encoded_offer).unwrap();
BigSize(EXPERIMENTAL_OFFER_TYPES.end).write(&mut encoded_offer).unwrap();
BigSize(32).write(&mut encoded_offer).unwrap();
[42u8; 32].write(&mut encoded_offer).unwrap();
@ -1953,6 +2098,9 @@ mod bolt12_tests {
// unknown odd field
"lno1pgx9getnwss8vetrw3hhyuckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxfppf5x2mrvdamk7unvvs",
// unknown odd experimental field
"lno1pgx9getnwss8vetrw3hhyuckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvx078wdv5gg2dpjkcmr0wahhymry",
];
for encoded_offer in &offers {
if let Err(e) = encoded_offer.parse::<Offer>() {
@ -2095,6 +2243,18 @@ mod bolt12_tests {
Err(Bolt12ParseError::Decode(DecodeError::InvalidValue)),
);
// Contains type > 1999999999
assert_eq!(
"lno1pgz5znzfgdz3vggzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgp06ae4jsq9qgr0u2xq4dh3kdevrf4zg6hx8a60jv0gxe0ptgyfc6xkryqqqqqqqq".parse::<Offer>(),
Err(Bolt12ParseError::Decode(DecodeError::InvalidValue)),
);
// Contains unknown even type (1000000002)
assert_eq!(
"lno1pgz5znzfgdz3vggzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgp06wu6egp9qgr0u2xq4dh3kdevrf4zg6hx8a60jv0gxe0ptgyfc6xkryqqqqqqqq".parse::<Offer>(),
Err(Bolt12ParseError::Decode(DecodeError::InvalidValue)),
);
// TODO: Resolved in spec https://github.com/lightning/bolts/pull/798/files#r1334851959
// Contains unknown feature 22
assert!(

View File

@ -30,6 +30,6 @@ pub(super) struct PayerContents(pub Metadata);
/// [`Refund::payer_metadata`]: crate::offers::refund::Refund::payer_metadata
pub(super) const PAYER_METADATA_TYPE: u64 = 0;
tlv_stream!(PayerTlvStream, PayerTlvStreamRef, 0..1, {
tlv_stream!(PayerTlvStream, PayerTlvStreamRef<'a>, 0..1, {
(PAYER_METADATA_TYPE, metadata: (Vec<u8>, WithoutLength)),
});

View File

@ -98,9 +98,9 @@ use crate::ln::channelmanager::PaymentId;
use crate::types::features::InvoiceRequestFeatures;
use crate::ln::inbound_payment::{ExpandedKey, IV_LEN};
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
use crate::offers::invoice_request::{InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef};
use crate::offers::invoice_request::{ExperimentalInvoiceRequestTlvStream, ExperimentalInvoiceRequestTlvStreamRef, InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef};
use crate::offers::nonce::Nonce;
use crate::offers::offer::{OfferTlvStream, OfferTlvStreamRef};
use crate::offers::offer::{ExperimentalOfferTlvStream, ExperimentalOfferTlvStreamRef, OfferTlvStream, OfferTlvStreamRef};
use crate::offers::parse::{Bech32Encode, Bolt12ParseError, Bolt12SemanticError, ParsedMessage};
use crate::offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef};
use crate::offers::signer::{Metadata, MetadataMaterial, self};
@ -176,6 +176,10 @@ macro_rules! refund_explicit_metadata_builder_methods { () => {
payer: PayerContents(metadata), description: String::new(), absolute_expiry: None,
issuer: None, chain: None, amount_msats, features: InvoiceRequestFeatures::empty(),
quantity: None, payer_signing_pubkey: signing_pubkey, payer_note: None, paths: None,
#[cfg(test)]
experimental_foo: None,
#[cfg(test)]
experimental_bar: None,
},
secp_ctx: None,
})
@ -218,6 +222,10 @@ macro_rules! refund_builder_methods { (
payer: PayerContents(metadata), description: String::new(), absolute_expiry: None,
issuer: None, chain: None, amount_msats, features: InvoiceRequestFeatures::empty(),
quantity: None, payer_signing_pubkey: node_id, payer_note: None, paths: None,
#[cfg(test)]
experimental_foo: None,
#[cfg(test)]
experimental_bar: None,
},
secp_ctx: Some(secp_ctx),
})
@ -358,6 +366,18 @@ macro_rules! refund_builder_test_methods { (
$self.refund.features = features;
$return_value
}
#[cfg_attr(c_bindings, allow(dead_code))]
pub(super) fn experimental_foo($($self_mut)* $self: $self_type, experimental_foo: u64) -> $return_type {
$self.refund.experimental_foo = Some(experimental_foo);
$return_value
}
#[cfg_attr(c_bindings, allow(dead_code))]
pub(super) fn experimental_bar($($self_mut)* $self: $self_type, experimental_bar: u64) -> $return_type {
$self.refund.experimental_bar = Some(experimental_bar);
$return_value
}
} }
impl<'a> RefundBuilder<'a, secp256k1::SignOnly> {
@ -437,6 +457,10 @@ pub(super) struct RefundContents {
payer_signing_pubkey: PublicKey,
payer_note: Option<String>,
paths: Option<Vec<BlindedMessagePath>>,
#[cfg(test)]
experimental_foo: Option<u64>,
#[cfg(test)]
experimental_bar: Option<u64>,
}
impl Refund {
@ -770,7 +794,17 @@ impl RefundContents {
paths: self.paths.as_ref(),
};
(payer, offer, invoice_request)
let experimental_offer = ExperimentalOfferTlvStreamRef {
#[cfg(test)]
experimental_foo: self.experimental_foo,
};
let experimental_invoice_request = ExperimentalInvoiceRequestTlvStreamRef {
#[cfg(test)]
experimental_bar: self.experimental_bar,
};
(payer, offer, invoice_request, experimental_offer, experimental_invoice_request)
}
}
@ -793,12 +827,17 @@ impl Writeable for RefundContents {
}
}
type RefundTlvStream = (PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream);
type RefundTlvStream = (
PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, ExperimentalOfferTlvStream,
ExperimentalInvoiceRequestTlvStream,
);
type RefundTlvStreamRef<'a> = (
PayerTlvStreamRef<'a>,
OfferTlvStreamRef<'a>,
InvoiceRequestTlvStreamRef<'a>,
ExperimentalOfferTlvStreamRef,
ExperimentalInvoiceRequestTlvStreamRef,
);
impl CursorReadable for RefundTlvStream {
@ -806,8 +845,10 @@ impl CursorReadable for RefundTlvStream {
let payer = CursorReadable::read(r)?;
let offer = CursorReadable::read(r)?;
let invoice_request = CursorReadable::read(r)?;
let experimental_offer = CursorReadable::read(r)?;
let experimental_invoice_request = CursorReadable::read(r)?;
Ok((payer, offer, invoice_request))
Ok((payer, offer, invoice_request, experimental_offer, experimental_invoice_request))
}
}
@ -849,6 +890,14 @@ impl TryFrom<RefundTlvStream> for RefundContents {
InvoiceRequestTlvStream {
chain, amount, features, quantity, payer_id, payer_note, paths
},
ExperimentalOfferTlvStream {
#[cfg(test)]
experimental_foo,
},
ExperimentalInvoiceRequestTlvStream {
#[cfg(test)]
experimental_bar,
},
) = tlv_stream;
let payer = match payer_metadata {
@ -909,6 +958,10 @@ impl TryFrom<RefundTlvStream> for RefundContents {
Ok(RefundContents {
payer, description, absolute_expiry, issuer, chain, amount_msats, features, quantity,
payer_signing_pubkey, payer_note, paths,
#[cfg(test)]
experimental_foo,
#[cfg(test)]
experimental_bar,
})
}
}
@ -944,9 +997,9 @@ mod tests {
use crate::types::features::{InvoiceRequestFeatures, OfferFeatures};
use crate::ln::inbound_payment::ExpandedKey;
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
use crate::offers::invoice_request::InvoiceRequestTlvStreamRef;
use crate::offers::invoice_request::{EXPERIMENTAL_INVOICE_REQUEST_TYPES, ExperimentalInvoiceRequestTlvStreamRef, INVOICE_REQUEST_TYPES, InvoiceRequestTlvStreamRef};
use crate::offers::nonce::Nonce;
use crate::offers::offer::OfferTlvStreamRef;
use crate::offers::offer::{ExperimentalOfferTlvStreamRef, OfferTlvStreamRef};
use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError};
use crate::offers::payer::PayerTlvStreamRef;
use crate::offers::test_utils::*;
@ -1014,6 +1067,12 @@ mod tests {
payer_note: None,
paths: None,
},
ExperimentalOfferTlvStreamRef {
experimental_foo: None,
},
ExperimentalInvoiceRequestTlvStreamRef {
experimental_bar: None,
},
),
);
@ -1042,6 +1101,8 @@ mod tests {
let refund = RefundBuilder
::deriving_signing_pubkey(node_id, &expanded_key, nonce, &secp_ctx, 1000, payment_id)
.unwrap()
.experimental_foo(42)
.experimental_bar(42)
.build().unwrap();
assert_eq!(refund.payer_signing_pubkey(), node_id);
@ -1049,6 +1110,7 @@ mod tests {
let invoice = refund
.respond_with_no_std(payment_paths(), payment_hash(), recipient_pubkey(), now())
.unwrap()
.experimental_baz(42)
.build().unwrap()
.sign(recipient_sign).unwrap();
match invoice.verify_using_metadata(&expanded_key, &secp_ctx) {
@ -1109,12 +1171,15 @@ mod tests {
::deriving_signing_pubkey(node_id, &expanded_key, nonce, &secp_ctx, 1000, payment_id)
.unwrap()
.path(blinded_path)
.experimental_foo(42)
.experimental_bar(42)
.build().unwrap();
assert_ne!(refund.payer_signing_pubkey(), node_id);
let invoice = refund
.respond_with_no_std(payment_paths(), payment_hash(), recipient_pubkey(), now())
.unwrap()
.experimental_baz(42)
.build().unwrap()
.sign(recipient_sign).unwrap();
assert!(invoice.verify_using_metadata(&expanded_key, &secp_ctx).is_err());
@ -1166,7 +1231,7 @@ mod tests {
.absolute_expiry(future_expiry)
.build()
.unwrap();
let (_, tlv_stream, _) = refund.as_tlv_stream();
let (_, tlv_stream, _, _, _) = refund.as_tlv_stream();
#[cfg(feature = "std")]
assert!(!refund.is_expired());
assert!(!refund.is_expired_no_std(now));
@ -1178,7 +1243,7 @@ mod tests {
.absolute_expiry(past_expiry)
.build()
.unwrap();
let (_, tlv_stream, _) = refund.as_tlv_stream();
let (_, tlv_stream, _, _, _) = refund.as_tlv_stream();
#[cfg(feature = "std")]
assert!(refund.is_expired());
assert!(refund.is_expired_no_std(now));
@ -1210,7 +1275,7 @@ mod tests {
.path(paths[1].clone())
.build()
.unwrap();
let (_, _, invoice_request_tlv_stream) = refund.as_tlv_stream();
let (_, _, invoice_request_tlv_stream, _, _) = refund.as_tlv_stream();
assert_eq!(refund.payer_signing_pubkey(), pubkey(42));
assert_eq!(refund.paths(), paths.as_slice());
assert_ne!(pubkey(42), pubkey(44));
@ -1224,7 +1289,7 @@ mod tests {
.issuer("bar".into())
.build()
.unwrap();
let (_, tlv_stream, _) = refund.as_tlv_stream();
let (_, tlv_stream, _, _, _) = refund.as_tlv_stream();
assert_eq!(refund.issuer(), Some(PrintableString("bar")));
assert_eq!(tlv_stream.issuer, Some(&String::from("bar")));
@ -1233,7 +1298,7 @@ mod tests {
.issuer("baz".into())
.build()
.unwrap();
let (_, tlv_stream, _) = refund.as_tlv_stream();
let (_, tlv_stream, _, _, _) = refund.as_tlv_stream();
assert_eq!(refund.issuer(), Some(PrintableString("baz")));
assert_eq!(tlv_stream.issuer, Some(&String::from("baz")));
}
@ -1246,14 +1311,14 @@ mod tests {
let refund = RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap()
.chain(Network::Bitcoin)
.build().unwrap();
let (_, _, tlv_stream) = refund.as_tlv_stream();
let (_, _, tlv_stream, _, _) = refund.as_tlv_stream();
assert_eq!(refund.chain(), mainnet);
assert_eq!(tlv_stream.chain, None);
let refund = RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap()
.chain(Network::Testnet)
.build().unwrap();
let (_, _, tlv_stream) = refund.as_tlv_stream();
let (_, _, tlv_stream, _, _) = refund.as_tlv_stream();
assert_eq!(refund.chain(), testnet);
assert_eq!(tlv_stream.chain, Some(&testnet));
@ -1261,7 +1326,7 @@ mod tests {
.chain(Network::Regtest)
.chain(Network::Testnet)
.build().unwrap();
let (_, _, tlv_stream) = refund.as_tlv_stream();
let (_, _, tlv_stream, _, _) = refund.as_tlv_stream();
assert_eq!(refund.chain(), testnet);
assert_eq!(tlv_stream.chain, Some(&testnet));
}
@ -1271,7 +1336,7 @@ mod tests {
let refund = RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap()
.quantity(10)
.build().unwrap();
let (_, _, tlv_stream) = refund.as_tlv_stream();
let (_, _, tlv_stream, _, _) = refund.as_tlv_stream();
assert_eq!(refund.quantity(), Some(10));
assert_eq!(tlv_stream.quantity, Some(10));
@ -1279,7 +1344,7 @@ mod tests {
.quantity(10)
.quantity(1)
.build().unwrap();
let (_, _, tlv_stream) = refund.as_tlv_stream();
let (_, _, tlv_stream, _, _) = refund.as_tlv_stream();
assert_eq!(refund.quantity(), Some(1));
assert_eq!(tlv_stream.quantity, Some(1));
}
@ -1289,7 +1354,7 @@ mod tests {
let refund = RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap()
.payer_note("bar".into())
.build().unwrap();
let (_, _, tlv_stream) = refund.as_tlv_stream();
let (_, _, tlv_stream, _, _) = refund.as_tlv_stream();
assert_eq!(refund.payer_note(), Some(PrintableString("bar")));
assert_eq!(tlv_stream.payer_note, Some(&String::from("bar")));
@ -1297,7 +1362,7 @@ mod tests {
.payer_note("bar".into())
.payer_note("baz".into())
.build().unwrap();
let (_, _, tlv_stream) = refund.as_tlv_stream();
let (_, _, tlv_stream, _, _) = refund.as_tlv_stream();
assert_eq!(refund.payer_note(), Some(PrintableString("baz")));
assert_eq!(tlv_stream.payer_note, Some(&String::from("baz")));
}
@ -1522,7 +1587,81 @@ mod tests {
}
#[test]
fn fails_parsing_refund_with_extra_tlv_records() {
fn parses_refund_with_unknown_tlv_records() {
const UNKNOWN_ODD_TYPE: u64 = INVOICE_REQUEST_TYPES.end - 1;
assert!(UNKNOWN_ODD_TYPE % 2 == 1);
let refund = RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap()
.build().unwrap();
let mut encoded_refund = Vec::new();
refund.write(&mut encoded_refund).unwrap();
BigSize(UNKNOWN_ODD_TYPE).write(&mut encoded_refund).unwrap();
BigSize(32).write(&mut encoded_refund).unwrap();
[42u8; 32].write(&mut encoded_refund).unwrap();
match Refund::try_from(encoded_refund.clone()) {
Ok(refund) => assert_eq!(refund.bytes, encoded_refund),
Err(e) => panic!("error parsing refund: {:?}", e),
}
const UNKNOWN_EVEN_TYPE: u64 = INVOICE_REQUEST_TYPES.end - 2;
assert!(UNKNOWN_EVEN_TYPE % 2 == 0);
let refund = RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap()
.build().unwrap();
let mut encoded_refund = Vec::new();
refund.write(&mut encoded_refund).unwrap();
BigSize(UNKNOWN_EVEN_TYPE).write(&mut encoded_refund).unwrap();
BigSize(32).write(&mut encoded_refund).unwrap();
[42u8; 32].write(&mut encoded_refund).unwrap();
match Refund::try_from(encoded_refund) {
Ok(_) => panic!("expected error"),
Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::UnknownRequiredFeature)),
}
}
#[test]
fn parses_refund_with_experimental_tlv_records() {
const UNKNOWN_ODD_TYPE: u64 = EXPERIMENTAL_INVOICE_REQUEST_TYPES.start + 1;
assert!(UNKNOWN_ODD_TYPE % 2 == 1);
let refund = RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap()
.build().unwrap();
let mut encoded_refund = Vec::new();
refund.write(&mut encoded_refund).unwrap();
BigSize(UNKNOWN_ODD_TYPE).write(&mut encoded_refund).unwrap();
BigSize(32).write(&mut encoded_refund).unwrap();
[42u8; 32].write(&mut encoded_refund).unwrap();
match Refund::try_from(encoded_refund.clone()) {
Ok(refund) => assert_eq!(refund.bytes, encoded_refund),
Err(e) => panic!("error parsing refund: {:?}", e),
}
const UNKNOWN_EVEN_TYPE: u64 = EXPERIMENTAL_INVOICE_REQUEST_TYPES.start;
assert!(UNKNOWN_EVEN_TYPE % 2 == 0);
let refund = RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap()
.build().unwrap();
let mut encoded_refund = Vec::new();
refund.write(&mut encoded_refund).unwrap();
BigSize(UNKNOWN_EVEN_TYPE).write(&mut encoded_refund).unwrap();
BigSize(32).write(&mut encoded_refund).unwrap();
[42u8; 32].write(&mut encoded_refund).unwrap();
match Refund::try_from(encoded_refund) {
Ok(_) => panic!("expected error"),
Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::UnknownRequiredFeature)),
}
}
#[test]
fn fails_parsing_refund_with_out_of_range_tlv_records() {
let secp_ctx = Secp256k1::new();
let keys = Keypair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
let refund = RefundBuilder::new(vec![1; 32], keys.public_key(), 1000).unwrap()
@ -1538,5 +1677,16 @@ mod tests {
Ok(_) => panic!("expected error"),
Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::InvalidValue)),
}
let mut encoded_refund = Vec::new();
refund.write(&mut encoded_refund).unwrap();
BigSize(EXPERIMENTAL_INVOICE_REQUEST_TYPES.end).write(&mut encoded_refund).unwrap();
BigSize(32).write(&mut encoded_refund).unwrap();
[42u8; 32].write(&mut encoded_refund).unwrap();
match Refund::try_from(encoded_refund) {
Ok(_) => panic!("expected error"),
Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::InvalidValue)),
}
}
}

View File

@ -15,17 +15,22 @@ use crate::io;
use crate::ln::inbound_payment::ExpandedKey;
use crate::ln::msgs::DecodeError;
use crate::offers::invoice::{
check_invoice_signing_pubkey, construct_payment_paths, filter_fallbacks, FallbackAddress,
check_invoice_signing_pubkey, construct_payment_paths, filter_fallbacks,
ExperimentalInvoiceTlvStream, ExperimentalInvoiceTlvStreamRef, FallbackAddress,
InvoiceTlvStream, InvoiceTlvStreamRef,
};
#[cfg(test)]
use crate::offers::invoice_macros::invoice_builder_methods_test;
use crate::offers::invoice_macros::{invoice_accessors_common, invoice_builder_methods_common};
use crate::offers::invoice_request::InvoiceRequest;
use crate::offers::merkle::{
self, SignError, SignFn, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, TlvStream,
SIGNATURE_TLV_RECORD_SIZE,
};
use crate::offers::nonce::Nonce;
use crate::offers::offer::{
Amount, Offer, OfferContents, OfferTlvStream, OfferTlvStreamRef, Quantity, OFFER_TYPES,
Amount, ExperimentalOfferTlvStream, ExperimentalOfferTlvStreamRef, Offer, OfferContents,
OfferTlvStream, OfferTlvStreamRef, Quantity, EXPERIMENTAL_OFFER_TYPES, OFFER_TYPES,
};
use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError, ParsedMessage};
use crate::types::features::{Bolt12InvoiceFeatures, OfferFeatures};
@ -79,6 +84,8 @@ struct InvoiceContents {
features: Bolt12InvoiceFeatures,
signing_pubkey: PublicKey,
message_paths: Vec<BlindedMessagePath>,
#[cfg(test)]
experimental_baz: Option<u64>,
}
/// Builds a [`StaticInvoice`] from an [`Offer`].
@ -130,10 +137,9 @@ impl<'a> StaticInvoiceBuilder<'a> {
Ok(Self { offer_bytes: &offer.bytes, invoice, keys })
}
/// Builds a signed [`StaticInvoice`] after checking for valid semantics.
pub fn build_and_sign<T: secp256k1::Signing>(
self, secp_ctx: &Secp256k1<T>,
) -> Result<StaticInvoice, Bolt12SemanticError> {
/// Builds an [`UnsignedStaticInvoice`] after checking for valid semantics, returning it along with
/// the [`Keypair`] needed to sign it.
pub fn build(self) -> Result<(UnsignedStaticInvoice, Keypair), Bolt12SemanticError> {
#[cfg(feature = "std")]
{
if self.invoice.is_offer_expired() {
@ -149,7 +155,14 @@ impl<'a> StaticInvoiceBuilder<'a> {
}
let Self { offer_bytes, invoice, keys } = self;
let unsigned_invoice = UnsignedStaticInvoice::new(&offer_bytes, invoice);
Ok((UnsignedStaticInvoice::new(&offer_bytes, invoice), keys))
}
/// Builds a signed [`StaticInvoice`] after checking for valid semantics.
pub fn build_and_sign<T: secp256k1::Signing>(
self, secp_ctx: &Secp256k1<T>,
) -> Result<StaticInvoice, Bolt12SemanticError> {
let (unsigned_invoice, keys) = self.build()?;
let invoice = unsigned_invoice
.sign(|message: &UnsignedStaticInvoice| {
Ok(secp_ctx.sign_schnorr_no_aux_rand(message.tagged_hash.as_digest(), &keys))
@ -159,11 +172,15 @@ impl<'a> StaticInvoiceBuilder<'a> {
}
invoice_builder_methods_common!(self, Self, self.invoice, Self, self, StaticInvoice, mut);
#[cfg(test)]
invoice_builder_methods_test!(self, Self, self.invoice, Self, self, mut);
}
/// A semantically valid [`StaticInvoice`] that hasn't been signed.
pub struct UnsignedStaticInvoice {
bytes: Vec<u8>,
experimental_bytes: Vec<u8>,
contents: InvoiceContents,
tagged_hash: TaggedHash,
}
@ -269,16 +286,50 @@ macro_rules! invoice_accessors_signing_pubkey {
impl UnsignedStaticInvoice {
fn new(offer_bytes: &Vec<u8>, contents: InvoiceContents) -> Self {
let (_, invoice_tlv_stream) = contents.as_tlv_stream();
let offer_bytes = WithoutLength(offer_bytes);
let unsigned_tlv_stream = (offer_bytes, invoice_tlv_stream);
let (_, invoice_tlv_stream, _, experimental_invoice_tlv_stream) = contents.as_tlv_stream();
let mut bytes = Vec::new();
unsigned_tlv_stream.write(&mut bytes).unwrap();
// Allocate enough space for the invoice, which will include:
// - all TLV records from `offer_bytes`,
// - all invoice-specific TLV records, and
// - a signature TLV record once the invoice is signed.
let mut bytes = Vec::with_capacity(
offer_bytes.len()
+ invoice_tlv_stream.serialized_length()
+ SIGNATURE_TLV_RECORD_SIZE
+ experimental_invoice_tlv_stream.serialized_length(),
);
let tagged_hash = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &bytes);
// Use the offer bytes instead of the offer TLV stream as the latter may have contained
// unknown TLV records, which are not stored in `InvoiceContents`.
for record in TlvStream::new(offer_bytes).range(OFFER_TYPES) {
record.write(&mut bytes).unwrap();
}
Self { contents, tagged_hash, bytes }
let remaining_bytes = &offer_bytes[bytes.len()..];
invoice_tlv_stream.write(&mut bytes).unwrap();
let mut experimental_tlv_stream =
TlvStream::new(remaining_bytes).range(EXPERIMENTAL_OFFER_TYPES).peekable();
let mut experimental_bytes = Vec::with_capacity(
remaining_bytes.len()
- experimental_tlv_stream
.peek()
.map_or(remaining_bytes.len(), |first_record| first_record.start)
+ experimental_invoice_tlv_stream.serialized_length(),
);
for record in experimental_tlv_stream {
record.write(&mut experimental_bytes).unwrap();
}
experimental_invoice_tlv_stream.write(&mut experimental_bytes).unwrap();
debug_assert_eq!(experimental_bytes.len(), experimental_bytes.capacity());
let tlv_stream = TlvStream::new(&bytes).chain(TlvStream::new(&experimental_bytes));
let tagged_hash = TaggedHash::from_tlv_stream(SIGNATURE_TAG, tlv_stream);
Self { bytes, experimental_bytes, contents, tagged_hash }
}
/// Signs the [`TaggedHash`] of the invoice using the given function.
@ -292,6 +343,15 @@ impl UnsignedStaticInvoice {
let signature_tlv_stream = SignatureTlvStreamRef { signature: Some(&signature) };
signature_tlv_stream.write(&mut self.bytes).unwrap();
// Append the experimental bytes after the signature.
debug_assert_eq!(
// The two-byte overallocation results from SIGNATURE_TLV_RECORD_SIZE accommodating TLV
// records with types >= 253.
self.bytes.len() + self.experimental_bytes.len() + 2,
self.bytes.capacity(),
);
self.bytes.extend_from_slice(&self.experimental_bytes);
Ok(StaticInvoice { bytes: self.bytes, contents: self.contents, signature })
}
@ -341,12 +401,10 @@ impl StaticInvoice {
}
pub(crate) fn from_same_offer(&self, invreq: &InvoiceRequest) -> bool {
let invoice_offer_tlv_stream = TlvStream::new(&self.bytes)
.range(OFFER_TYPES)
.map(|tlv_record| tlv_record.record_bytes);
let invreq_offer_tlv_stream = TlvStream::new(invreq.bytes())
.range(OFFER_TYPES)
.map(|tlv_record| tlv_record.record_bytes);
let invoice_offer_tlv_stream =
Offer::tlv_stream_iter(&self.bytes).map(|tlv_record| tlv_record.record_bytes);
let invreq_offer_tlv_stream =
Offer::tlv_stream_iter(invreq.bytes()).map(|tlv_record| tlv_record.record_bytes);
invoice_offer_tlv_stream.eq(invreq_offer_tlv_stream)
}
}
@ -375,6 +433,8 @@ impl InvoiceContents {
fallbacks: None,
features: Bolt12InvoiceFeatures::empty(),
signing_pubkey,
#[cfg(test)]
experimental_baz: None,
}
}
@ -400,7 +460,14 @@ impl InvoiceContents {
payment_hash: None,
};
(self.offer.as_tlv_stream(), invoice)
let experimental_invoice = ExperimentalInvoiceTlvStreamRef {
#[cfg(test)]
experimental_baz: self.experimental_baz,
};
let (offer, experimental_offer) = self.offer.as_tlv_stream();
(offer, invoice, experimental_offer, experimental_invoice)
}
fn chain(&self) -> ChainHash {
@ -497,29 +564,54 @@ impl TryFrom<Vec<u8>> for StaticInvoice {
}
}
type FullInvoiceTlvStream = (OfferTlvStream, InvoiceTlvStream, SignatureTlvStream);
type FullInvoiceTlvStream = (
OfferTlvStream,
InvoiceTlvStream,
SignatureTlvStream,
ExperimentalOfferTlvStream,
ExperimentalInvoiceTlvStream,
);
impl CursorReadable for FullInvoiceTlvStream {
fn read<R: AsRef<[u8]>>(r: &mut io::Cursor<R>) -> Result<Self, DecodeError> {
let offer = CursorReadable::read(r)?;
let invoice = CursorReadable::read(r)?;
let signature = CursorReadable::read(r)?;
let experimental_offer = CursorReadable::read(r)?;
let experimental_invoice = CursorReadable::read(r)?;
Ok((offer, invoice, signature))
Ok((offer, invoice, signature, experimental_offer, experimental_invoice))
}
}
type PartialInvoiceTlvStream = (OfferTlvStream, InvoiceTlvStream);
type PartialInvoiceTlvStream =
(OfferTlvStream, InvoiceTlvStream, ExperimentalOfferTlvStream, ExperimentalInvoiceTlvStream);
type PartialInvoiceTlvStreamRef<'a> = (OfferTlvStreamRef<'a>, InvoiceTlvStreamRef<'a>);
type PartialInvoiceTlvStreamRef<'a> = (
OfferTlvStreamRef<'a>,
InvoiceTlvStreamRef<'a>,
ExperimentalOfferTlvStreamRef,
ExperimentalInvoiceTlvStreamRef,
);
impl TryFrom<ParsedMessage<FullInvoiceTlvStream>> for StaticInvoice {
type Error = Bolt12ParseError;
fn try_from(invoice: ParsedMessage<FullInvoiceTlvStream>) -> Result<Self, Self::Error> {
let ParsedMessage { bytes, tlv_stream } = invoice;
let (offer_tlv_stream, invoice_tlv_stream, SignatureTlvStream { signature }) = tlv_stream;
let contents = InvoiceContents::try_from((offer_tlv_stream, invoice_tlv_stream))?;
let (
offer_tlv_stream,
invoice_tlv_stream,
SignatureTlvStream { signature },
experimental_offer_tlv_stream,
experimental_invoice_tlv_stream,
) = tlv_stream;
let contents = InvoiceContents::try_from((
offer_tlv_stream,
invoice_tlv_stream,
experimental_offer_tlv_stream,
experimental_invoice_tlv_stream,
))?;
let signature = match signature {
None => {
@ -555,6 +647,11 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
payment_hash,
amount,
},
experimental_offer_tlv_stream,
ExperimentalInvoiceTlvStream {
#[cfg(test)]
experimental_baz,
},
) = tlv_stream;
if payment_hash.is_some() {
@ -587,7 +684,7 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
}
Ok(InvoiceContents {
offer: OfferContents::try_from(offer_tlv_stream)?,
offer: OfferContents::try_from((offer_tlv_stream, experimental_offer_tlv_stream))?,
payment_paths,
message_paths,
created_at,
@ -595,6 +692,8 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
fallbacks,
features,
signing_pubkey,
#[cfg(test)]
experimental_baz,
})
}
}
@ -605,14 +704,20 @@ mod tests {
use crate::blinded_path::BlindedHop;
use crate::ln::inbound_payment::ExpandedKey;
use crate::ln::msgs::DecodeError;
use crate::offers::invoice::InvoiceTlvStreamRef;
use crate::offers::invoice::{
ExperimentalInvoiceTlvStreamRef, InvoiceTlvStreamRef, EXPERIMENTAL_INVOICE_TYPES,
INVOICE_TYPES,
};
use crate::offers::merkle;
use crate::offers::merkle::{SignatureTlvStreamRef, TaggedHash};
use crate::offers::merkle::{SignatureTlvStreamRef, TaggedHash, TlvStream};
use crate::offers::nonce::Nonce;
use crate::offers::offer::{Offer, OfferBuilder, OfferTlvStreamRef, Quantity};
use crate::offers::offer::{
ExperimentalOfferTlvStreamRef, Offer, OfferBuilder, OfferTlvStreamRef, Quantity,
};
use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError};
use crate::offers::static_invoice::{
StaticInvoice, StaticInvoiceBuilder, DEFAULT_RELATIVE_EXPIRY, SIGNATURE_TAG,
StaticInvoice, StaticInvoiceBuilder, UnsignedStaticInvoice, DEFAULT_RELATIVE_EXPIRY,
SIGNATURE_TAG,
};
use crate::offers::test_utils::*;
use crate::sign::KeyMaterial;
@ -623,27 +728,47 @@ mod tests {
use bitcoin::Network;
use core::time::Duration;
type FullInvoiceTlvStreamRef<'a> =
(OfferTlvStreamRef<'a>, InvoiceTlvStreamRef<'a>, SignatureTlvStreamRef<'a>);
type FullInvoiceTlvStreamRef<'a> = (
OfferTlvStreamRef<'a>,
InvoiceTlvStreamRef<'a>,
SignatureTlvStreamRef<'a>,
ExperimentalOfferTlvStreamRef,
ExperimentalInvoiceTlvStreamRef,
);
impl StaticInvoice {
fn as_tlv_stream(&self) -> FullInvoiceTlvStreamRef {
let (offer_tlv_stream, invoice_tlv_stream) = self.contents.as_tlv_stream();
let (
offer_tlv_stream,
invoice_tlv_stream,
experimental_offer_tlv_stream,
experimental_invoice_tlv_stream,
) = self.contents.as_tlv_stream();
(
offer_tlv_stream,
invoice_tlv_stream,
SignatureTlvStreamRef { signature: Some(&self.signature) },
experimental_offer_tlv_stream,
experimental_invoice_tlv_stream,
)
}
}
fn tlv_stream_to_bytes(
tlv_stream: &(OfferTlvStreamRef, InvoiceTlvStreamRef, SignatureTlvStreamRef),
tlv_stream: &(
OfferTlvStreamRef,
InvoiceTlvStreamRef,
SignatureTlvStreamRef,
ExperimentalOfferTlvStreamRef,
ExperimentalInvoiceTlvStreamRef,
),
) -> Vec<u8> {
let mut buffer = Vec::new();
tlv_stream.0.write(&mut buffer).unwrap();
tlv_stream.1.write(&mut buffer).unwrap();
tlv_stream.2.write(&mut buffer).unwrap();
tlv_stream.3.write(&mut buffer).unwrap();
tlv_stream.4.write(&mut buffer).unwrap();
buffer
}
@ -773,6 +898,8 @@ mod tests {
message_paths: Some(&paths),
},
SignatureTlvStreamRef { signature: Some(&invoice.signature()) },
ExperimentalOfferTlvStreamRef { experimental_foo: None },
ExperimentalInvoiceTlvStreamRef { experimental_baz: None },
)
);
@ -840,6 +967,52 @@ mod tests {
}
}
#[test]
fn builds_invoice_from_offer_using_derived_key() {
let node_id = recipient_pubkey();
let now = now();
let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
let entropy = FixedEntropy {};
let nonce = Nonce::from_entropy_source(&entropy);
let secp_ctx = Secp256k1::new();
let offer = OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, nonce, &secp_ctx)
.path(blinded_path())
.experimental_foo(42)
.build()
.unwrap();
if let Err(e) = StaticInvoiceBuilder::for_offer_using_derived_keys(
&offer,
payment_paths(),
vec![blinded_path()],
now,
&expanded_key,
nonce,
&secp_ctx,
)
.unwrap()
.build_and_sign(&secp_ctx)
{
panic!("error building invoice: {:?}", e);
}
let expanded_key = ExpandedKey::new(&KeyMaterial([41; 32]));
if let Err(e) = StaticInvoiceBuilder::for_offer_using_derived_keys(
&offer,
payment_paths(),
vec![blinded_path()],
now,
&expanded_key,
nonce,
&secp_ctx,
) {
assert_eq!(e, Bolt12SemanticError::InvalidMetadata);
} else {
panic!("expected error")
}
}
#[test]
fn fails_build_with_missing_paths() {
let node_id = recipient_pubkey();
@ -887,7 +1060,7 @@ mod tests {
// Error if offer paths are missing.
let mut offer_without_paths = valid_offer.clone();
let mut offer_tlv_stream = offer_without_paths.as_tlv_stream();
let (mut offer_tlv_stream, _) = offer_without_paths.as_tlv_stream();
offer_tlv_stream.paths.take();
let mut buffer = Vec::new();
offer_tlv_stream.write(&mut buffer).unwrap();
@ -923,7 +1096,7 @@ mod tests {
.unwrap();
let mut offer_missing_issuer_id = valid_offer.clone();
let mut offer_tlv_stream = offer_missing_issuer_id.as_tlv_stream();
let (mut offer_tlv_stream, _) = offer_missing_issuer_id.as_tlv_stream();
offer_tlv_stream.issuer_id.take();
let mut buffer = Vec::new();
offer_tlv_stream.write(&mut buffer).unwrap();
@ -1185,7 +1358,263 @@ mod tests {
}
#[test]
fn fails_parsing_invoice_with_extra_tlv_records() {
fn parses_invoice_with_unknown_tlv_records() {
let node_id = recipient_pubkey();
let payment_paths = payment_paths();
let now = now();
let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
let entropy = FixedEntropy {};
let nonce = Nonce::from_entropy_source(&entropy);
let secp_ctx = Secp256k1::new();
let offer = OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, nonce, &secp_ctx)
.path(blinded_path())
.build()
.unwrap();
const UNKNOWN_ODD_TYPE: u64 = INVOICE_TYPES.end - 1;
assert!(UNKNOWN_ODD_TYPE % 2 == 1);
let (mut unsigned_invoice, keys) = StaticInvoiceBuilder::for_offer_using_derived_keys(
&offer,
payment_paths.clone(),
vec![blinded_path()],
now,
&expanded_key,
nonce,
&secp_ctx,
)
.unwrap()
.build()
.unwrap();
let mut unknown_bytes = Vec::new();
BigSize(UNKNOWN_ODD_TYPE).write(&mut unknown_bytes).unwrap();
BigSize(32).write(&mut unknown_bytes).unwrap();
[42u8; 32].write(&mut unknown_bytes).unwrap();
unsigned_invoice.bytes.reserve_exact(
unsigned_invoice.bytes.capacity() - unsigned_invoice.bytes.len() + unknown_bytes.len(),
);
unsigned_invoice.bytes.extend_from_slice(&unknown_bytes);
unsigned_invoice.tagged_hash =
TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &unsigned_invoice.bytes);
let invoice = unsigned_invoice
.sign(|message: &UnsignedStaticInvoice| {
Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
})
.unwrap();
let mut encoded_invoice = Vec::new();
invoice.write(&mut encoded_invoice).unwrap();
match StaticInvoice::try_from(encoded_invoice.clone()) {
Ok(invoice) => assert_eq!(invoice.bytes, encoded_invoice),
Err(e) => panic!("error parsing invoice: {:?}", e),
}
const UNKNOWN_EVEN_TYPE: u64 = INVOICE_TYPES.end - 2;
assert!(UNKNOWN_EVEN_TYPE % 2 == 0);
let (mut unsigned_invoice, keys) = StaticInvoiceBuilder::for_offer_using_derived_keys(
&offer,
payment_paths.clone(),
vec![blinded_path()],
now,
&expanded_key,
nonce,
&secp_ctx,
)
.unwrap()
.build()
.unwrap();
let mut unknown_bytes = Vec::new();
BigSize(UNKNOWN_EVEN_TYPE).write(&mut unknown_bytes).unwrap();
BigSize(32).write(&mut unknown_bytes).unwrap();
[42u8; 32].write(&mut unknown_bytes).unwrap();
unsigned_invoice.bytes.reserve_exact(
unsigned_invoice.bytes.capacity() - unsigned_invoice.bytes.len() + unknown_bytes.len(),
);
unsigned_invoice.bytes.extend_from_slice(&unknown_bytes);
unsigned_invoice.tagged_hash =
TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &unsigned_invoice.bytes);
let invoice = unsigned_invoice
.sign(|message: &UnsignedStaticInvoice| {
Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
})
.unwrap();
let mut encoded_invoice = Vec::new();
invoice.write(&mut encoded_invoice).unwrap();
match StaticInvoice::try_from(encoded_invoice) {
Ok(_) => panic!("expected error"),
Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::UnknownRequiredFeature)),
}
}
#[test]
fn parses_invoice_with_experimental_tlv_records() {
let node_id = recipient_pubkey();
let payment_paths = payment_paths();
let now = now();
let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
let entropy = FixedEntropy {};
let nonce = Nonce::from_entropy_source(&entropy);
let secp_ctx = Secp256k1::new();
let offer = OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, nonce, &secp_ctx)
.path(blinded_path())
.build()
.unwrap();
let invoice = StaticInvoiceBuilder::for_offer_using_derived_keys(
&offer,
payment_paths.clone(),
vec![blinded_path()],
now,
&expanded_key,
nonce,
&secp_ctx,
)
.unwrap()
.experimental_baz(42)
.build_and_sign(&secp_ctx)
.unwrap();
let mut encoded_invoice = Vec::new();
invoice.write(&mut encoded_invoice).unwrap();
assert!(StaticInvoice::try_from(encoded_invoice).is_ok());
const UNKNOWN_ODD_TYPE: u64 = EXPERIMENTAL_INVOICE_TYPES.start + 1;
assert!(UNKNOWN_ODD_TYPE % 2 == 1);
let (mut unsigned_invoice, keys) = StaticInvoiceBuilder::for_offer_using_derived_keys(
&offer,
payment_paths.clone(),
vec![blinded_path()],
now,
&expanded_key,
nonce,
&secp_ctx,
)
.unwrap()
.build()
.unwrap();
let mut unknown_bytes = Vec::new();
BigSize(UNKNOWN_ODD_TYPE).write(&mut unknown_bytes).unwrap();
BigSize(32).write(&mut unknown_bytes).unwrap();
[42u8; 32].write(&mut unknown_bytes).unwrap();
unsigned_invoice.bytes.reserve_exact(
unsigned_invoice.bytes.capacity() - unsigned_invoice.bytes.len() + unknown_bytes.len(),
);
unsigned_invoice.experimental_bytes.extend_from_slice(&unknown_bytes);
let tlv_stream = TlvStream::new(&unsigned_invoice.bytes)
.chain(TlvStream::new(&unsigned_invoice.experimental_bytes));
unsigned_invoice.tagged_hash = TaggedHash::from_tlv_stream(SIGNATURE_TAG, tlv_stream);
let invoice = unsigned_invoice
.sign(|message: &UnsignedStaticInvoice| {
Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
})
.unwrap();
let mut encoded_invoice = Vec::new();
invoice.write(&mut encoded_invoice).unwrap();
match StaticInvoice::try_from(encoded_invoice.clone()) {
Ok(invoice) => assert_eq!(invoice.bytes, encoded_invoice),
Err(e) => panic!("error parsing invoice: {:?}", e),
}
const UNKNOWN_EVEN_TYPE: u64 = EXPERIMENTAL_INVOICE_TYPES.start;
assert!(UNKNOWN_EVEN_TYPE % 2 == 0);
let (mut unsigned_invoice, keys) = StaticInvoiceBuilder::for_offer_using_derived_keys(
&offer,
payment_paths.clone(),
vec![blinded_path()],
now,
&expanded_key,
nonce,
&secp_ctx,
)
.unwrap()
.build()
.unwrap();
let mut unknown_bytes = Vec::new();
BigSize(UNKNOWN_EVEN_TYPE).write(&mut unknown_bytes).unwrap();
BigSize(32).write(&mut unknown_bytes).unwrap();
[42u8; 32].write(&mut unknown_bytes).unwrap();
unsigned_invoice.bytes.reserve_exact(
unsigned_invoice.bytes.capacity() - unsigned_invoice.bytes.len() + unknown_bytes.len(),
);
unsigned_invoice.experimental_bytes.extend_from_slice(&unknown_bytes);
let tlv_stream = TlvStream::new(&unsigned_invoice.bytes)
.chain(TlvStream::new(&unsigned_invoice.experimental_bytes));
unsigned_invoice.tagged_hash = TaggedHash::from_tlv_stream(SIGNATURE_TAG, tlv_stream);
let invoice = unsigned_invoice
.sign(|message: &UnsignedStaticInvoice| {
Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
})
.unwrap();
let mut encoded_invoice = Vec::new();
invoice.write(&mut encoded_invoice).unwrap();
match StaticInvoice::try_from(encoded_invoice) {
Ok(_) => panic!("expected error"),
Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::UnknownRequiredFeature)),
}
let offer = OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, nonce, &secp_ctx)
.path(blinded_path())
.build()
.unwrap();
let invoice = StaticInvoiceBuilder::for_offer_using_derived_keys(
&offer,
payment_paths.clone(),
vec![blinded_path()],
now,
&expanded_key,
nonce,
&secp_ctx,
)
.unwrap()
.build_and_sign(&secp_ctx)
.unwrap();
let mut encoded_invoice = Vec::new();
invoice.write(&mut encoded_invoice).unwrap();
BigSize(UNKNOWN_ODD_TYPE).write(&mut encoded_invoice).unwrap();
BigSize(32).write(&mut encoded_invoice).unwrap();
[42u8; 32].write(&mut encoded_invoice).unwrap();
match StaticInvoice::try_from(encoded_invoice) {
Ok(_) => panic!("expected error"),
Err(e) => assert_eq!(
e,
Bolt12ParseError::InvalidSignature(secp256k1::Error::IncorrectSignature)
),
}
}
#[test]
fn fails_parsing_invoice_with_out_of_range_tlv_records() {
let invoice = invoice();
let mut encoded_invoice = Vec::new();
invoice.write(&mut encoded_invoice).unwrap();

View File

@ -1402,53 +1402,38 @@ impl<T: Writeable> Writeable for RwLock<T> {
}
}
impl<A: Readable, B: Readable> Readable for (A, B) {
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
let a: A = Readable::read(r)?;
let b: B = Readable::read(r)?;
Ok((a, b))
}
}
impl<A: Writeable, B: Writeable> Writeable for (A, B) {
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
self.0.write(w)?;
self.1.write(w)
macro_rules! impl_tuple_ser {
($($i: ident : $type: tt),*) => {
impl<$($type),*> Readable for ($($type),*)
where $(
$type: Readable,
)*
{
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
Ok(($(<$type as Readable>::read(r)?),*))
}
}
impl<$($type),*> Writeable for ($($type),*)
where $(
$type: Writeable,
)*
{
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
let ($($i),*) = self;
$($i.write(w)?;)*
Ok(())
}
}
}
}
impl<A: Readable, B: Readable, C: Readable> Readable for (A, B, C) {
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
let a: A = Readable::read(r)?;
let b: B = Readable::read(r)?;
let c: C = Readable::read(r)?;
Ok((a, b, c))
}
}
impl<A: Writeable, B: Writeable, C: Writeable> Writeable for (A, B, C) {
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
self.0.write(w)?;
self.1.write(w)?;
self.2.write(w)
}
}
impl<A: Readable, B: Readable, C: Readable, D: Readable> Readable for (A, B, C, D) {
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
let a: A = Readable::read(r)?;
let b: B = Readable::read(r)?;
let c: C = Readable::read(r)?;
let d: D = Readable::read(r)?;
Ok((a, b, c, d))
}
}
impl<A: Writeable, B: Writeable, C: Writeable, D: Writeable> Writeable for (A, B, C, D) {
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
self.0.write(w)?;
self.1.write(w)?;
self.2.write(w)?;
self.3.write(w)
}
}
impl_tuple_ser!(a: A, b: B);
impl_tuple_ser!(a: A, b: B, c: C);
impl_tuple_ser!(a: A, b: B, c: C, d: D);
impl_tuple_ser!(a: A, b: B, c: C, d: D, e: E);
impl_tuple_ser!(a: A, b: B, c: C, d: D, e: E, f: F);
impl_tuple_ser!(a: A, b: B, c: C, d: D, e: E, f: F, g: G);
impl Writeable for () {
fn write<W: Writer>(&self, _: &mut W) -> Result<(), io::Error> {

View File

@ -952,7 +952,7 @@ macro_rules! impl_writeable_tlv_based {
/// [`Readable`]: crate::util::ser::Readable
/// [`Writeable`]: crate::util::ser::Writeable
macro_rules! tlv_stream {
($name:ident, $nameref:ident, $range:expr, {
($name:ident, $nameref:ident $(<$lifetime:lifetime>)?, $range:expr, {
$(($type:expr, $field:ident : $fieldty:tt)),* $(,)*
}) => {
#[derive(Debug)]
@ -964,13 +964,13 @@ macro_rules! tlv_stream {
#[cfg_attr(test, derive(PartialEq))]
#[derive(Debug)]
pub(crate) struct $nameref<'a> {
pub(crate) struct $nameref<$($lifetime)*> {
$(
pub(super) $field: Option<tlv_record_ref_type!($fieldty)>,
)*
}
impl<'a> $crate::util::ser::Writeable for $nameref<'a> {
impl<$($lifetime)*> $crate::util::ser::Writeable for $nameref<$($lifetime)*> {
fn write<W: $crate::util::ser::Writer>(&self, writer: &mut W) -> Result<(), $crate::io::Error> {
encode_tlv_stream!(writer, {
$(($type, self.$field, (option, encoding: $fieldty))),*