diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index 9616b6f54b9..f747301c3f4 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -57,7 +57,6 @@ use lightning::ln::msgs::{ use lightning::ln::script::ShutdownScript; use lightning::ln::types::{ChannelId, PaymentHash, PaymentPreimage, PaymentSecret}; use lightning::offers::invoice::UnsignedBolt12Invoice; -use lightning::offers::invoice_request::UnsignedInvoiceRequest; use lightning::onion_message::messenger::{Destination, MessageRouter, OnionMessagePath}; use lightning::routing::router::{InFlightHtlcs, Path, Route, RouteHop, RouteParameters, Router}; use lightning::sign::{ @@ -339,12 +338,6 @@ impl NodeSigner for KeyProvider { unreachable!() } - fn sign_bolt12_invoice_request( - &self, _invoice_request: &UnsignedInvoiceRequest, - ) -> Result { - unreachable!() - } - fn sign_bolt12_invoice( &self, _invoice: &UnsignedBolt12Invoice, ) -> Result { diff --git a/fuzz/src/full_stack.rs b/fuzz/src/full_stack.rs index 8ce17c93dbd..fe57e0e445f 100644 --- a/fuzz/src/full_stack.rs +++ b/fuzz/src/full_stack.rs @@ -50,7 +50,6 @@ use lightning::ln::peer_handler::{ use lightning::ln::script::ShutdownScript; use lightning::ln::types::{ChannelId, PaymentHash, PaymentPreimage, PaymentSecret}; use lightning::offers::invoice::UnsignedBolt12Invoice; -use lightning::offers::invoice_request::UnsignedInvoiceRequest; use lightning::onion_message::messenger::{Destination, MessageRouter, OnionMessagePath}; use lightning::routing::gossip::{NetworkGraph, P2PGossipSync}; use lightning::routing::router::{ @@ -412,12 +411,6 @@ impl NodeSigner for KeyProvider { unreachable!() } - fn sign_bolt12_invoice_request( - &self, _invoice_request: &UnsignedInvoiceRequest, - ) -> Result { - unreachable!() - } - fn sign_bolt12_invoice( &self, _invoice: &UnsignedBolt12Invoice, ) -> Result { diff --git a/fuzz/src/offer_deser.rs b/fuzz/src/offer_deser.rs index 84b69d3fcd3..f903e48851d 100644 --- a/fuzz/src/offer_deser.rs +++ b/fuzz/src/offer_deser.rs @@ -8,11 +8,15 @@ // licenses. use crate::utils::test_logger; -use bitcoin::secp256k1::{Keypair, PublicKey, Secp256k1, SecretKey}; +use bitcoin::secp256k1::Secp256k1; use core::convert::TryFrom; -use lightning::offers::invoice_request::UnsignedInvoiceRequest; +use lightning::ln::channelmanager::PaymentId; +use lightning::ln::inbound_payment::ExpandedKey; +use lightning::offers::invoice_request::InvoiceRequest; +use lightning::offers::nonce::Nonce; use lightning::offers::offer::{Amount, Offer, Quantity}; use lightning::offers::parse::Bolt12SemanticError; +use lightning::sign::{EntropySource, KeyMaterial}; use lightning::util::ser::Writeable; #[inline] @@ -22,27 +26,30 @@ pub fn do_test(data: &[u8], _out: Out) { offer.write(&mut bytes).unwrap(); assert_eq!(data, bytes); - let secp_ctx = Secp256k1::new(); - let keys = Keypair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap()); - let pubkey = PublicKey::from(keys); let mut buffer = Vec::new(); - if let Ok(invoice_request) = build_response(&offer, pubkey) { - invoice_request - .sign(|message: &UnsignedInvoiceRequest| { - Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) - }) - .unwrap() - .write(&mut buffer) - .unwrap(); + if let Ok(invoice_request) = build_request(&offer) { + invoice_request.write(&mut buffer).unwrap(); } } } -fn build_response( - offer: &Offer, pubkey: PublicKey, -) -> Result { - let mut builder = offer.request_invoice(vec![42; 64], pubkey)?; +struct FixedEntropy; + +impl EntropySource for FixedEntropy { + fn get_secure_random_bytes(&self) -> [u8; 32] { + [42; 32] + } +} + +fn build_request(offer: &Offer) -> Result { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + + let mut builder = offer.request_invoice(&expanded_key, nonce, &secp_ctx, payment_id)?; builder = match offer.amount() { None => builder.amount_msats(1000).unwrap(), @@ -56,7 +63,7 @@ fn build_response( Quantity::One => builder, }; - builder.build() + builder.build_and_sign() } pub fn offer_deser_test(data: &[u8], out: Out) { diff --git a/fuzz/src/onion_message.rs b/fuzz/src/onion_message.rs index 5cd45238df2..fd3b530351e 100644 --- a/fuzz/src/onion_message.rs +++ b/fuzz/src/onion_message.rs @@ -13,7 +13,6 @@ use lightning::ln::features::InitFeatures; use lightning::ln::msgs::{self, DecodeError, OnionMessageHandler}; use lightning::ln::script::ShutdownScript; use lightning::offers::invoice::UnsignedBolt12Invoice; -use lightning::offers::invoice_request::UnsignedInvoiceRequest; use lightning::onion_message::async_payments::{ AsyncPaymentsMessageHandler, HeldHtlcAvailable, ReleaseHeldHtlc, }; @@ -232,12 +231,6 @@ impl NodeSigner for KeyProvider { unreachable!() } - fn sign_bolt12_invoice_request( - &self, _invoice_request: &UnsignedInvoiceRequest, - ) -> Result { - unreachable!() - } - fn sign_bolt12_invoice( &self, _invoice: &UnsignedBolt12Invoice, ) -> Result { diff --git a/lightning/src/ln/blinded_payment_tests.rs b/lightning/src/ln/blinded_payment_tests.rs index d099e439ae5..46bfdd1d015 100644 --- a/lightning/src/ln/blinded_payment_tests.rs +++ b/lightning/src/ln/blinded_payment_tests.rs @@ -26,7 +26,6 @@ use crate::ln::onion_utils; use crate::ln::onion_utils::INVALID_ONION_BLINDING; use crate::ln::outbound_payment::{Retry, IDEMPOTENCY_TIMEOUT_TICKS}; use crate::offers::invoice::UnsignedBolt12Invoice; -use crate::offers::invoice_request::UnsignedInvoiceRequest; use crate::prelude::*; use crate::routing::router::{BlindedTail, Path, Payee, PaymentParameters, RouteHop, RouteParameters}; use crate::sign::{KeyMaterial, NodeSigner, Recipient}; @@ -1466,9 +1465,6 @@ fn route_blinding_spec_test_vector() { fn sign_invoice( &self, _invoice: &RawBolt11Invoice, _recipient: Recipient, ) -> Result { unreachable!() } - fn sign_bolt12_invoice_request( - &self, _invoice_request: &UnsignedInvoiceRequest, - ) -> Result { unreachable!() } fn sign_bolt12_invoice( &self, _invoice: &UnsignedBolt12Invoice, ) -> Result { unreachable!() } diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 7e13d56667f..472cdaebf68 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -65,7 +65,7 @@ use crate::ln::outbound_payment::{OutboundPayments, PendingOutboundPayment, Retr use crate::ln::wire::Encode; use crate::offers::invoice::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder, UnsignedBolt12Invoice}; use crate::offers::invoice_error::InvoiceError; -use crate::offers::invoice_request::{DerivedPayerSigningPubkey, InvoiceRequest, InvoiceRequestBuilder}; +use crate::offers::invoice_request::{InvoiceRequest, InvoiceRequestBuilder}; use crate::offers::nonce::Nonce; use crate::offers::offer::{Offer, OfferBuilder}; use crate::offers::parse::Bolt12SemanticError; @@ -9310,8 +9310,8 @@ where let secp_ctx = &self.secp_ctx; let nonce = Nonce::from_entropy_source(entropy); - let builder: InvoiceRequestBuilder = offer - .request_invoice_deriving_signing_pubkey(expanded_key, nonce, secp_ctx, payment_id)? + let builder: InvoiceRequestBuilder = offer + .request_invoice(expanded_key, nonce, secp_ctx, payment_id)? .into(); let builder = builder.chain_hash(self.chain_hash)?; diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs index 2bc1c9b4edc..218652dfd19 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -2268,14 +2268,17 @@ mod tests { use crate::ln::types::{PaymentHash, PaymentPreimage}; use crate::ln::channelmanager::{PaymentId, RecipientOnionFields}; use crate::ln::features::{Bolt12InvoiceFeatures, ChannelFeatures, NodeFeatures}; + use crate::ln::inbound_payment::ExpandedKey; use crate::ln::msgs::{ErrorAction, LightningError}; use crate::ln::outbound_payment::{Bolt12PaymentError, OutboundPayments, PendingOutboundPayment, Retry, RetryableSendFailure, StaleExpiration}; #[cfg(feature = "std")] use crate::offers::invoice::DEFAULT_RELATIVE_EXPIRY; + use crate::offers::nonce::Nonce; use crate::offers::offer::OfferBuilder; use crate::offers::test_utils::*; use crate::routing::gossip::NetworkGraph; use crate::routing::router::{InFlightHtlcs, Path, PaymentParameters, Route, RouteHop, RouteParameters}; + use crate::sign::KeyMaterial; use crate::sync::{Arc, Mutex, RwLock}; use crate::util::errors::APIError; use crate::util::hash_tables::new_hash_map; @@ -2620,6 +2623,8 @@ mod tests { let router = test_utils::TestRouter::new(network_graph, &logger, &scorer); let secp_ctx = Secp256k1::new(); let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet); + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let nonce = Nonce([0; 16]); let pending_events = Mutex::new(VecDeque::new()); let outbound_payments = OutboundPayments::new(new_hash_map()); @@ -2637,9 +2642,8 @@ mod tests { let invoice = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap() .respond_with_no_std(payment_paths(), payment_hash(), created_at).unwrap() .build().unwrap() .sign(recipient_sign).unwrap(); @@ -2676,15 +2680,16 @@ mod tests { let pending_events = Mutex::new(VecDeque::new()); let outbound_payments = OutboundPayments::new(new_hash_map()); + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let nonce = Nonce([0; 16]); let payment_id = PaymentId([0; 32]); let expiration = StaleExpiration::AbsoluteTimeout(Duration::from_secs(100)); let invoice = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .build().unwrap() .sign(recipient_sign).unwrap(); @@ -2737,15 +2742,16 @@ mod tests { let pending_events = Mutex::new(VecDeque::new()); let outbound_payments = OutboundPayments::new(new_hash_map()); + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let nonce = Nonce([0; 16]); let payment_id = PaymentId([0; 32]); let expiration = StaleExpiration::AbsoluteTimeout(Duration::from_secs(100)); let invoice = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .build().unwrap() .sign(recipient_sign).unwrap(); diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index 648c0fba651..c65fecbeff2 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -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}; 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, + experimental_bytes: Vec, contents: InvoiceContents, tagged_hash: TaggedHash, } @@ -491,19 +494,36 @@ where impl UnsignedBolt12Invoice { fn new(invreq_bytes: &[u8], contents: InvoiceContents) -> Self { + const NON_EXPERIMENTAL_TYPES: core::ops::Range = 0..INVOICE_REQUEST_TYPES.end; + const EXPERIMENTAL_TYPES: core::ops::Range = + EXPERIMENTAL_OFFER_TYPES.start..EXPERIMENTAL_INVOICE_REQUEST_TYPES.end; + + let mut bytes = Vec::new(); + // 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 (_, _, _, invoice_tlv_stream, _, _, experimental_invoice_tlv_stream) = + contents.as_tlv_stream(); - let tagged_hash = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &bytes); + invoice_tlv_stream.write(&mut bytes).unwrap(); + + let mut experimental_bytes = Vec::new(); + + for record in TlvStream::new(invreq_bytes).range(EXPERIMENTAL_TYPES) { + record.write(&mut experimental_bytes).unwrap(); + } + + experimental_invoice_tlv_stream.write(&mut experimental_bytes).unwrap(); - Self { bytes, contents, tagged_hash } + 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 +548,9 @@ 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. + WithoutLength(&$self.experimental_bytes).write(&mut $self.bytes).unwrap(); + Ok(Bolt12Invoice { #[cfg(not(c_bindings))] bytes: $self.bytes, @@ -612,6 +635,8 @@ struct InvoiceFields { fallbacks: Option>, features: Bolt12InvoiceFeatures, signing_pubkey: PublicKey, + #[cfg(test)] + experimental_baz: Option, } macro_rules! invoice_accessors { ($self: ident, $contents: expr) => { @@ -828,7 +853,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 +867,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 +876,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 { @@ -1086,18 +1118,22 @@ impl InvoiceContents { } fn verify( - &self, tlv_stream: TlvStream<'_>, metadata: &Metadata, key: &ExpandedKey, - iv_bytes: &[u8; IV_LEN], secp_ctx: &Secp256k1 + &self, bytes: &[u8], metadata: &Metadata, key: &ExpandedKey, iv_bytes: &[u8; IV_LEN], + secp_ctx: &Secp256k1, ) -> Result { - 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 = + 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 +1142,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 +1201,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 +1258,30 @@ impl TryFrom> for UnsignedBolt12Invoice { fn try_from(bytes: Vec) -> Result { let invoice = ParsedMessage::::try_from(bytes)?; - let ParsedMessage { bytes, tlv_stream } = invoice; + let ParsedMessage { mut bytes, tlv_stream } = invoice; 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, ) = 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 tagged_hash = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &bytes); - Ok(UnsignedBolt12Invoice { bytes, contents, tagged_hash }) + let mut offset = 0; + for tlv_record in TlvStream::new(&bytes).range(0..INVOICE_TYPES.end) { + offset = tlv_record.end; + } + + let experimental_bytes = bytes.split_off(offset); + + Ok(UnsignedBolt12Invoice { bytes, experimental_bytes, contents, tagged_hash }) } } @@ -1234,7 +1294,10 @@ impl TryFrom> for Bolt12Invoice { } } -tlv_stream!(InvoiceTlvStream, InvoiceTlvStreamRef, 160..240, { +/// Valid type range for invoice TLV records. +pub(super) const INVOICE_TYPES: core::ops::Range = 160..240; + +tlv_stream!(InvoiceTlvStream, InvoiceTlvStreamRef<'a>, INVOICE_TYPES, { (160, paths: (Vec, WithoutLength, Iterable<'a, BlindedPathIter<'a>, BlindedPath>)), (162, blindedpay: (Vec, WithoutLength, Iterable<'a, BlindedPayInfoIter<'a>, BlindedPayInfo>)), (164, created_at: (u64, HighZeroBytesDroppedBigSize)), @@ -1245,9 +1308,24 @@ tlv_stream!(InvoiceTlvStream, InvoiceTlvStreamRef, 160..240, { (174, features: (Bolt12InvoiceFeatures, WithoutLength)), (176, node_id: PublicKey), // Only present in `StaticInvoice`s. - (238, message_paths: (Vec, WithoutLength)), + (236, message_paths: (Vec, WithoutLength)), }); +/// Valid type range for experimental invoice TLV records. +pub(super) const EXPERIMENTAL_INVOICE_TYPES: core::ops::RangeFrom = 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 +1345,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 +1356,9 @@ type FullInvoiceTlvStreamRef<'a> = ( InvoiceRequestTlvStreamRef<'a>, InvoiceTlvStreamRef<'a>, SignatureTlvStreamRef<'a>, + ExperimentalOfferTlvStreamRef, + ExperimentalInvoiceRequestTlvStreamRef, + ExperimentalInvoiceTlvStreamRef, ); impl CursorReadable for FullInvoiceTlvStream { @@ -1285,19 +1368,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 +1402,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 +1423,16 @@ impl TryFrom> 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 +1458,12 @@ impl TryFrom 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 +1490,26 @@ impl TryFrom 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 +1562,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; @@ -1453,13 +1578,14 @@ mod tests { use crate::blinded_path::BlindedHop; use crate::blinded_path::message::BlindedMessagePath; use crate::sign::KeyMaterial; + use crate::ln::channelmanager::PaymentId; use crate::ln::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 { @@ -1495,15 +1621,21 @@ mod tests { #[test] fn builds_invoice_for_offer_with_defaults() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + let encrypted_payment_id = expanded_key.crypt_for_offer(payment_id.0, nonce); + let payment_paths = payment_paths(); let payment_hash = payment_hash(); let now = now(); let unsigned_invoice = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap() .respond_with_no_std(payment_paths.clone(), payment_hash, now).unwrap() .build().unwrap(); @@ -1511,7 +1643,7 @@ mod tests { unsigned_invoice.write(&mut buffer).unwrap(); assert_eq!(unsigned_invoice.bytes, buffer.as_slice()); - assert_eq!(unsigned_invoice.payer_metadata(), &[1; 32]); + assert_eq!(unsigned_invoice.payer_metadata(), &encrypted_payment_id); assert_eq!(unsigned_invoice.offer_chains(), Some(vec![ChainHash::using_genesis_block(Network::Bitcoin)])); assert_eq!(unsigned_invoice.metadata(), None); assert_eq!(unsigned_invoice.amount(), Some(Amount::Bitcoin { amount_msats: 1000 })); @@ -1526,7 +1658,6 @@ mod tests { assert_eq!(unsigned_invoice.amount_msats(), 1000); assert_eq!(unsigned_invoice.invoice_request_features(), &InvoiceRequestFeatures::empty()); assert_eq!(unsigned_invoice.quantity(), None); - assert_eq!(unsigned_invoice.payer_signing_pubkey(), payer_pubkey()); assert_eq!(unsigned_invoice.payer_note(), None); assert_eq!(unsigned_invoice.payment_paths(), payment_paths.as_slice()); assert_eq!(unsigned_invoice.created_at(), now); @@ -1553,7 +1684,7 @@ mod tests { invoice.write(&mut buffer).unwrap(); assert_eq!(invoice.bytes, buffer.as_slice()); - assert_eq!(invoice.payer_metadata(), &[1; 32]); + assert_eq!(invoice.payer_metadata(), &encrypted_payment_id); assert_eq!(invoice.offer_chains(), Some(vec![ChainHash::using_genesis_block(Network::Bitcoin)])); assert_eq!(invoice.metadata(), None); assert_eq!(invoice.amount(), Some(Amount::Bitcoin { amount_msats: 1000 })); @@ -1568,7 +1699,10 @@ mod tests { assert_eq!(invoice.amount_msats(), 1000); assert_eq!(invoice.invoice_request_features(), &InvoiceRequestFeatures::empty()); assert_eq!(invoice.quantity(), None); - assert_eq!(invoice.payer_signing_pubkey(), payer_pubkey()); + assert_eq!( + invoice.verify_using_payer_data(payment_id, nonce, &expanded_key, &secp_ctx), + Ok(payment_id), + ); assert_eq!(invoice.payer_note(), None); assert_eq!(invoice.payment_paths(), payment_paths.as_slice()); assert_eq!(invoice.created_at(), now); @@ -1591,7 +1725,7 @@ mod tests { assert_eq!( invoice.as_tlv_stream(), ( - PayerTlvStreamRef { metadata: Some(&vec![1; 32]) }, + PayerTlvStreamRef { metadata: Some(&encrypted_payment_id.to_vec()) }, OfferTlvStreamRef { chains: None, metadata: None, @@ -1610,7 +1744,7 @@ mod tests { amount: None, features: None, quantity: None, - payer_id: Some(&payer_pubkey()), + payer_id: Some(&invoice.payer_signing_pubkey()), payer_note: None, paths: None, }, @@ -1627,6 +1761,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 +1863,15 @@ mod tests { message_paths: None, }, SignatureTlvStreamRef { signature: Some(&invoice.signature()) }, + ExperimentalOfferTlvStreamRef { + experimental_foo: None, + }, + ExperimentalInvoiceRequestTlvStreamRef { + experimental_bar: None, + }, + ExperimentalInvoiceTlvStreamRef { + experimental_baz: None, + }, ), ); @@ -1731,6 +1883,12 @@ mod tests { #[cfg(feature = "std")] #[test] fn builds_invoice_from_offer_with_expiration() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + let future_expiry = Duration::from_secs(u64::max_value()); let past_expiry = Duration::from_secs(0); @@ -1738,9 +1896,8 @@ mod tests { .amount_msats(1000) .absolute_expiry(future_expiry) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap() .respond_with(payment_paths(), payment_hash()) .unwrap() .build() @@ -1752,9 +1909,8 @@ mod tests { .amount_msats(1000) .absolute_expiry(past_expiry) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build_unchecked() - .sign(payer_sign).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_unchecked_and_sign() .respond_with(payment_paths(), payment_hash()) .unwrap() .build() @@ -1799,6 +1955,7 @@ mod tests { let entropy = FixedEntropy {}; let nonce = Nonce::from_entropy_source(&entropy); let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); let blinded_path = BlindedMessagePath::from_raw( pubkey(40), pubkey(41), @@ -1810,13 +1967,14 @@ mod tests { #[cfg(c_bindings)] use crate::offers::offer::OfferWithDerivedMetadataBuilder as OfferBuilder; - let offer = OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, nonce, &secp_ctx) + let invoice_request = OfferBuilder + ::deriving_signing_pubkey(node_id, &expanded_key, nonce, &secp_ctx) .amount_msats(1000) .path(blinded_path) - .build().unwrap(); - let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .experimental_foo(42) .build().unwrap() - .sign(payer_sign).unwrap(); + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap(); if let Err(e) = invoice_request.clone() .verify_using_recipient_data(nonce, &expanded_key, &secp_ctx).unwrap() @@ -1831,13 +1989,14 @@ mod tests { invoice_request.verify_using_recipient_data(nonce, &expanded_key, &secp_ctx).is_err() ); - let offer = OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, nonce, &secp_ctx) + let invoice_request = 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 - .build().unwrap(); - let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .experimental_foo(42) .build().unwrap() - .sign(payer_sign).unwrap(); + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap(); match invoice_request .verify_using_metadata(&expanded_key, &secp_ctx).unwrap() @@ -1855,6 +2014,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 @@ -1900,20 +2060,25 @@ mod tests { #[test] fn builds_invoice_with_relative_expiry() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + let now = now(); let one_hour = Duration::from_secs(3600); let invoice = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now).unwrap() .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); @@ -1922,14 +2087,13 @@ mod tests { let invoice = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now - one_hour).unwrap() .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)); @@ -1938,35 +2102,45 @@ mod tests { #[test] fn builds_invoice_with_amount_from_request() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + let invoice = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() .amount_msats(1001).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap() + .build_and_sign().unwrap() .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)); } #[test] fn builds_invoice_with_quantity_from_request() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + let invoice = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .supported_quantity(Quantity::Unbounded) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() .quantity(2).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap() + .build_and_sign().unwrap() .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)); @@ -1974,10 +2148,9 @@ mod tests { .amount_msats(1000) .supported_quantity(Quantity::Unbounded) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() .quantity(u64::max_value()).unwrap() - .build_unchecked() - .sign(payer_sign).unwrap() + .build_unchecked_and_sign() .respond_with_no_std(payment_paths(), payment_hash(), now()) { Ok(_) => panic!("expected error"), @@ -1987,6 +2160,12 @@ mod tests { #[test] fn builds_invoice_with_fallback_address() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + let script = ScriptBuf::new(); let pubkey = bitcoin::key::PublicKey::new(recipient_pubkey()); let x_only_pubkey = XOnlyPublicKey::from_keypair(&recipient_keys()).0; @@ -1995,16 +2174,15 @@ mod tests { let invoice = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .fallback_v0_p2wsh(&script.wscript_hash()) .fallback_v0_p2wpkh(&pubkey.wpubkey_hash().unwrap()) .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![ @@ -2034,32 +2212,42 @@ mod tests { #[test] fn builds_invoice_with_allow_mpp() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + let mut features = Bolt12InvoiceFeatures::empty(); features.set_basic_mpp_optional(); let invoice = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .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)); } #[test] fn fails_signing_invoice() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + match OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .build().unwrap() .sign(fail_sign) @@ -2071,9 +2259,8 @@ mod tests { match OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .build().unwrap() .sign(payer_sign) @@ -2085,12 +2272,17 @@ mod tests { #[test] fn parses_invoice_with_payment_paths() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + let invoice = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .build().unwrap() .sign(recipient_sign).unwrap(); @@ -2140,12 +2332,17 @@ mod tests { #[test] fn parses_invoice_with_created_at() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + let invoice = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .build().unwrap() .sign(recipient_sign).unwrap(); @@ -2170,12 +2367,17 @@ mod tests { #[test] fn parses_invoice_with_relative_expiry() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + let invoice = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .relative_expiry(3600) .build().unwrap() @@ -2192,12 +2394,17 @@ mod tests { #[test] fn parses_invoice_with_payment_hash() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + let invoice = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .build().unwrap() .sign(recipient_sign).unwrap(); @@ -2222,12 +2429,17 @@ mod tests { #[test] fn parses_invoice_with_amount() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + let invoice = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .build().unwrap() .sign(recipient_sign).unwrap(); @@ -2250,12 +2462,17 @@ mod tests { #[test] fn parses_invoice_with_allow_mpp() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + let invoice = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .allow_mpp() .build().unwrap() @@ -2276,18 +2493,22 @@ mod tests { #[test] fn parses_invoice_with_fallback_address() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + let script = ScriptBuf::new(); let pubkey = bitcoin::key::PublicKey::new(recipient_pubkey()); let x_only_pubkey = XOnlyPublicKey::from_keypair(&recipient_keys()).0; let tweaked_pubkey = TweakedPublicKey::dangerous_assume_tweaked(x_only_pubkey); - let offer = OfferBuilder::new(recipient_pubkey()) + let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) - .build().unwrap(); - let invoice_request = offer - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() .build().unwrap() - .sign(payer_sign).unwrap(); + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap(); #[cfg(not(c_bindings))] let invoice_builder = invoice_request .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap(); @@ -2336,12 +2557,17 @@ mod tests { #[test] fn parses_invoice_with_node_id() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + let invoice = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .build().unwrap() .sign(recipient_sign).unwrap(); @@ -2377,6 +2603,12 @@ mod tests { #[test] fn parses_invoice_with_node_id_from_blinded_path() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + let paths = vec![ BlindedMessagePath::from_raw( pubkey(40), pubkey(41), @@ -2406,9 +2638,8 @@ mod tests { .path(paths[0].clone()) .path(paths[1].clone()) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap() .respond_with_no_std_using_signing_pubkey( payment_paths(), payment_hash(), now(), pubkey(46) ).unwrap() @@ -2428,9 +2659,8 @@ mod tests { .path(paths[0].clone()) .path(paths[1].clone()) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap() .respond_with_no_std_using_signing_pubkey( payment_paths(), payment_hash(), now(), recipient_pubkey() ).unwrap() @@ -2450,13 +2680,18 @@ mod tests { #[test] fn fails_parsing_invoice_without_signature() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + let mut buffer = Vec::new(); OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .build().unwrap() .contents @@ -2470,12 +2705,17 @@ mod tests { #[test] fn fails_parsing_invoice_with_invalid_signature() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + let mut invoice = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .build().unwrap() .sign(recipient_sign).unwrap(); @@ -2494,13 +2734,184 @@ mod tests { } #[test] - fn fails_parsing_invoice_with_extra_tlv_records() { - let invoice = OfferBuilder::new(recipient_pubkey()) + fn parses_invoice_with_unknown_tlv_records() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let payment_id = PaymentId([1; 32]); + + 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(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap() + .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() + .build().unwrap(); + + BigSize(UNKNOWN_ODD_TYPE).write(&mut unsigned_invoice.bytes).unwrap(); + BigSize(32).write(&mut unsigned_invoice.bytes).unwrap(); + [42u8; 32].write(&mut unsigned_invoice.bytes).unwrap(); + + 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(); + + if let Err(e) = Bolt12Invoice::try_from(encoded_invoice) { + 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(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap() + .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() + .build().unwrap(); + + BigSize(UNKNOWN_EVEN_TYPE).write(&mut unsigned_invoice.bytes).unwrap(); + BigSize(32).write(&mut unsigned_invoice.bytes).unwrap(); + [42u8; 32].write(&mut unsigned_invoice.bytes).unwrap(); + + 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 expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let payment_id = PaymentId([1; 32]); + + 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() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_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 buffer = Vec::new(); + invoice.write(&mut buffer).unwrap(); + + assert!(Bolt12Invoice::try_from(buffer).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() - .sign(payer_sign).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap() + .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() + .build().unwrap(); + + BigSize(UNKNOWN_ODD_TYPE).write(&mut unsigned_invoice.experimental_bytes).unwrap(); + BigSize(32).write(&mut unsigned_invoice.experimental_bytes).unwrap(); + [42u8; 32].write(&mut unsigned_invoice.experimental_bytes).unwrap(); + + 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(); + + if let Err(e) = Bolt12Invoice::try_from(encoded_invoice) { + 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(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap() + .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() + .build().unwrap(); + + BigSize(UNKNOWN_EVEN_TYPE).write(&mut unsigned_invoice.experimental_bytes).unwrap(); + BigSize(32).write(&mut unsigned_invoice.experimental_bytes).unwrap(); + [42u8; 32].write(&mut unsigned_invoice.experimental_bytes).unwrap(); + + 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)), + } + } + + #[test] + fn fails_parsing_invoice_with_out_of_range_tlv_records() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + + let invoice = OfferBuilder::new(recipient_pubkey()) + .amount_msats(1000) + .build().unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .build().unwrap() .sign(recipient_sign).unwrap(); @@ -2519,12 +2930,17 @@ mod tests { #[test] fn fails_parsing_invoice_with_message_paths() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + let invoice = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .build().unwrap() .sign(recipient_sign).unwrap(); diff --git a/lightning/src/offers/invoice_macros.rs b/lightning/src/offers/invoice_macros.rs index 579ecd2d20a..4a540c16046 100644 --- a/lightning/src/offers/invoice_macros.rs +++ b/lightning/src/offers/invoice_macros.rs @@ -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) => { diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index c6c9da82a4e..dc836241eb6 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -25,32 +25,41 @@ //! //! use bitcoin::network::Network; //! use bitcoin::secp256k1::{Keypair, PublicKey, Secp256k1, SecretKey}; +//! use lightning::ln::channelmanager::PaymentId; //! use lightning::ln::features::OfferFeatures; +//! use lightning::ln::inbound_payment::ExpandedKey; //! use lightning::offers::invoice_request::UnsignedInvoiceRequest; +//! # use lightning::offers::nonce::Nonce; //! use lightning::offers::offer::Offer; +//! # use lightning::sign::EntropySource; +//! use lightning::sign::KeyMaterial; //! use lightning::util::ser::Writeable; //! +//! # struct FixedEntropy; +//! # impl EntropySource for FixedEntropy { +//! # fn get_secure_random_bytes(&self) -> [u8; 32] { +//! # [42; 32] +//! # } +//! # } //! # fn parse() -> Result<(), lightning::offers::parse::Bolt12ParseError> { +//! let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); +//! # let entropy = FixedEntropy {}; +//! # let nonce = Nonce::from_entropy_source(&entropy); //! let secp_ctx = Secp256k1::new(); -//! let keys = Keypair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32])?); -//! let pubkey = PublicKey::from(keys); +//! let payment_id = PaymentId([1; 32]); //! let mut buffer = Vec::new(); //! -//! # use lightning::offers::invoice_request::{ExplicitPayerSigningPubkey, InvoiceRequestBuilder}; -//! # >::from( +//! # use lightning::offers::invoice_request::InvoiceRequestBuilder; +//! # >::from( //! "lno1qcp4256ypq" //! .parse::()? -//! .request_invoice(vec![42; 64], pubkey)? +//! .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id)? //! # ) //! .chain(Network::Testnet)? //! .amount_msats(1000)? //! .quantity(5)? //! .payer_note("foo".to_string()) -//! .build()? -//! .sign(|message: &UnsignedInvoiceRequest| -//! Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) -//! ) -//! .expect("failed verifying signature") +//! .build_and_sign()? //! .write(&mut buffer) //! .unwrap(); //! # Ok(()) @@ -69,9 +78,9 @@ use crate::ln::channelmanager::PaymentId; use crate::ln::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}; 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}; @@ -102,28 +111,13 @@ pub(super) const IV_BYTES: &[u8; IV_LEN] = b"LDK Invreq ~~~~~"; /// This is not exported to bindings users as builder patterns don't map outside of move semantics. /// /// [module-level documentation]: self -pub struct InvoiceRequestBuilder<'a, 'b, P: PayerSigningPubkeyStrategy, T: secp256k1::Signing> { +pub struct InvoiceRequestBuilder<'a, 'b, T: secp256k1::Signing> { offer: &'a Offer, invoice_request: InvoiceRequestContentsWithoutPayerSigningPubkey, payer_signing_pubkey: Option, - payer_signing_pubkey_strategy: core::marker::PhantomData

, secp_ctx: Option<&'b Secp256k1>, } -/// Builds an [`InvoiceRequest`] from an [`Offer`] for the "offer to be paid" flow. -/// -/// See [module-level documentation] for usage. -/// -/// [module-level documentation]: self -#[cfg(c_bindings)] -pub struct InvoiceRequestWithExplicitPayerSigningPubkeyBuilder<'a, 'b> { - offer: &'a Offer, - invoice_request: InvoiceRequestContentsWithoutPayerSigningPubkey, - payer_signing_pubkey: Option, - payer_signing_pubkey_strategy: core::marker::PhantomData, - secp_ctx: Option<&'b Secp256k1>, -} - /// Builds an [`InvoiceRequest`] from an [`Offer`] for the "offer to be paid" flow. /// /// See [module-level documentation] for usage. @@ -134,66 +128,9 @@ pub struct InvoiceRequestWithDerivedPayerSigningPubkeyBuilder<'a, 'b> { offer: &'a Offer, invoice_request: InvoiceRequestContentsWithoutPayerSigningPubkey, payer_signing_pubkey: Option, - payer_signing_pubkey_strategy: core::marker::PhantomData, secp_ctx: Option<&'b Secp256k1>, } -/// Indicates how [`InvoiceRequest::payer_signing_pubkey`] will be set. -/// -/// This is not exported to bindings users as builder patterns don't map outside of move semantics. -pub trait PayerSigningPubkeyStrategy {} - -/// [`InvoiceRequest::payer_signing_pubkey`] will be explicitly set. -/// -/// This is not exported to bindings users as builder patterns don't map outside of move semantics. -pub struct ExplicitPayerSigningPubkey {} - -/// [`InvoiceRequest::payer_signing_pubkey`] will be derived. -/// -/// This is not exported to bindings users as builder patterns don't map outside of move semantics. -pub struct DerivedPayerSigningPubkey {} - -impl PayerSigningPubkeyStrategy for ExplicitPayerSigningPubkey {} -impl PayerSigningPubkeyStrategy for DerivedPayerSigningPubkey {} - -macro_rules! invoice_request_explicit_payer_signing_pubkey_builder_methods { ($self: ident, $self_type: ty) => { - #[cfg_attr(c_bindings, allow(dead_code))] - pub(super) fn new(offer: &'a Offer, metadata: Vec, signing_pubkey: PublicKey) -> Self { - Self { - offer, - invoice_request: Self::create_contents(offer, Metadata::Bytes(metadata)), - payer_signing_pubkey: Some(signing_pubkey), - payer_signing_pubkey_strategy: core::marker::PhantomData, - secp_ctx: None, - } - } - - #[cfg_attr(c_bindings, allow(dead_code))] - pub(super) fn deriving_metadata( - offer: &'a Offer, signing_pubkey: PublicKey, expanded_key: &ExpandedKey, nonce: Nonce, - payment_id: PaymentId, - ) -> Self { - let payment_id = Some(payment_id); - let derivation_material = MetadataMaterial::new(nonce, expanded_key, payment_id); - let metadata = Metadata::Derived(derivation_material); - Self { - offer, - invoice_request: Self::create_contents(offer, metadata), - payer_signing_pubkey: Some(signing_pubkey), - payer_signing_pubkey_strategy: core::marker::PhantomData, - secp_ctx: None, - } - } - - /// Builds an unsigned [`InvoiceRequest`] after checking for valid semantics. It can be signed - /// by [`UnsignedInvoiceRequest::sign`]. - pub fn build($self: $self_type) -> Result { - let (unsigned_invoice_request, keys, _) = $self.build_with_checks()?; - debug_assert!(keys.is_none()); - Ok(unsigned_invoice_request) - } -} } - macro_rules! invoice_request_derived_payer_signing_pubkey_builder_methods { ( $self: ident, $self_type: ty, $secp_context: ty ) => { @@ -209,7 +146,6 @@ macro_rules! invoice_request_derived_payer_signing_pubkey_builder_methods { ( offer, invoice_request: Self::create_contents(offer, metadata), payer_signing_pubkey: None, - payer_signing_pubkey_strategy: core::marker::PhantomData, secp_ctx: Some(secp_ctx), } } @@ -241,6 +177,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, } } @@ -379,6 +317,12 @@ macro_rules! invoice_request_builder_methods { ( macro_rules! invoice_request_builder_test_methods { ( $self: ident, $self_type: ty, $return_type: ty, $return_value: expr $(, $self_mut: tt)? ) => { + #[cfg_attr(c_bindings, allow(dead_code))] + pub(super) fn payer_metadata($($self_mut)* $self: $self_type, metadata: Metadata) -> $return_type { + $self.invoice_request.payer = PayerContents(metadata); + $return_value + } + #[cfg_attr(c_bindings, allow(dead_code))] fn chain_unchecked($($self_mut)* $self: $self_type, network: Network) -> $return_type { let chain = ChainHash::using_genesis_block(network); @@ -404,40 +348,48 @@ macro_rules! invoice_request_builder_test_methods { ( $return_value } + #[cfg_attr(c_bindings, allow(dead_code))] + pub(super) fn payer_signing_pubkey($($self_mut)* $self: $self_type, signing_pubkey: PublicKey) -> $return_type { + $self.payer_signing_pubkey = Some(signing_pubkey); + $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 } -} } -impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, ExplicitPayerSigningPubkey, T> { - invoice_request_explicit_payer_signing_pubkey_builder_methods!(self, Self); -} + #[cfg_attr(c_bindings, allow(dead_code))] + pub(super) fn build_unchecked_and_sign($self: $self_type) -> InvoiceRequest { + let (unsigned_invoice_request, keys, secp_ctx) = $self.build_without_checks(); + #[cfg(c_bindings)] + let mut unsigned_invoice_request = unsigned_invoice_request; + debug_assert!(keys.is_some()); -impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, DerivedPayerSigningPubkey, T> { - invoice_request_derived_payer_signing_pubkey_builder_methods!(self, Self, T); -} + let secp_ctx = secp_ctx.unwrap(); + let keys = keys.unwrap(); + unsigned_invoice_request + .sign(|message: &UnsignedInvoiceRequest| + Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) + ) + .unwrap() + } +} } -impl<'a, 'b, P: PayerSigningPubkeyStrategy, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, P, T> { +impl<'a, 'b, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, T> { + invoice_request_derived_payer_signing_pubkey_builder_methods!(self, Self, T); invoice_request_builder_methods!(self, Self, Self, self, T, mut); #[cfg(test)] invoice_request_builder_test_methods!(self, Self, Self, self, mut); } -#[cfg(all(c_bindings, not(test)))] -impl<'a, 'b> InvoiceRequestWithExplicitPayerSigningPubkeyBuilder<'a, 'b> { - invoice_request_explicit_payer_signing_pubkey_builder_methods!(self, &mut Self); - invoice_request_builder_methods!(self, &mut Self, (), (), secp256k1::All); -} - -#[cfg(all(c_bindings, test))] -impl<'a, 'b> InvoiceRequestWithExplicitPayerSigningPubkeyBuilder<'a, 'b> { - invoice_request_explicit_payer_signing_pubkey_builder_methods!(self, &mut Self); - invoice_request_builder_methods!(self, &mut Self, &mut Self, self, secp256k1::All); - invoice_request_builder_test_methods!(self, &mut Self, &mut Self, self); -} - #[cfg(all(c_bindings, not(test)))] impl<'a, 'b> InvoiceRequestWithDerivedPayerSigningPubkeyBuilder<'a, 'b> { invoice_request_derived_payer_signing_pubkey_builder_methods!(self, &mut Self, secp256k1::All); @@ -451,30 +403,16 @@ impl<'a, 'b> InvoiceRequestWithDerivedPayerSigningPubkeyBuilder<'a, 'b> { invoice_request_builder_test_methods!(self, &mut Self, &mut Self, self); } -#[cfg(c_bindings)] -impl<'a, 'b> From> -for InvoiceRequestBuilder<'a, 'b, ExplicitPayerSigningPubkey, secp256k1::All> { - fn from(builder: InvoiceRequestWithExplicitPayerSigningPubkeyBuilder<'a, 'b>) -> Self { - let InvoiceRequestWithExplicitPayerSigningPubkeyBuilder { - offer, invoice_request, payer_signing_pubkey, payer_signing_pubkey_strategy, secp_ctx, - } = builder; - - Self { - offer, invoice_request, payer_signing_pubkey, payer_signing_pubkey_strategy, secp_ctx, - } - } -} - #[cfg(c_bindings)] impl<'a, 'b> From> -for InvoiceRequestBuilder<'a, 'b, DerivedPayerSigningPubkey, secp256k1::All> { +for InvoiceRequestBuilder<'a, 'b, secp256k1::All> { fn from(builder: InvoiceRequestWithDerivedPayerSigningPubkeyBuilder<'a, 'b>) -> Self { let InvoiceRequestWithDerivedPayerSigningPubkeyBuilder { - offer, invoice_request, payer_signing_pubkey, payer_signing_pubkey_strategy, secp_ctx, + offer, invoice_request, payer_signing_pubkey, secp_ctx, } = builder; Self { - offer, invoice_request, payer_signing_pubkey, payer_signing_pubkey_strategy, secp_ctx, + offer, invoice_request, payer_signing_pubkey, secp_ctx, } } } @@ -488,6 +426,7 @@ for InvoiceRequestBuilder<'a, 'b, DerivedPayerSigningPubkey, secp256k1::All> { #[derive(Clone)] pub struct UnsignedInvoiceRequest { bytes: Vec, + experimental_bytes: Vec, contents: InvoiceRequestContents, tagged_hash: TaggedHash, } @@ -518,19 +457,35 @@ where impl UnsignedInvoiceRequest { fn new(offer: &Offer, contents: InvoiceRequestContents) -> Self { + let mut bytes = Vec::new(); + // 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(); + payer_tlv_stream.write(&mut bytes).unwrap(); - let tagged_hash = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &bytes); + for record in TlvStream::new(&offer.bytes).range(OFFER_TYPES) { + record.write(&mut bytes).unwrap(); + } + + invoice_request_tlv_stream.write(&mut bytes).unwrap(); + + let mut experimental_bytes = Vec::new(); + + for record in TlvStream::new(&offer.bytes).range(EXPERIMENTAL_OFFER_TYPES) { + record.write(&mut experimental_bytes).unwrap(); + } + + experimental_invoice_request_tlv_stream.write(&mut experimental_bytes).unwrap(); - Self { bytes, contents, tagged_hash } + 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 +512,9 @@ macro_rules! unsigned_invoice_request_sign_method { ( }; signature_tlv_stream.write(&mut $self.bytes).unwrap(); + // Append the experimental bytes after the signature. + WithoutLength(&$self.experimental_bytes).write(&mut $self.bytes).unwrap(); + Ok(InvoiceRequest { #[cfg(not(c_bindings))] bytes: $self.bytes, @@ -643,6 +601,8 @@ pub(super) struct InvoiceRequestContentsWithoutPayerSigningPubkey { features: InvoiceRequestFeatures, quantity: Option, payer_note: Option, + #[cfg(test)] + experimental_bar: Option, } macro_rules! invoice_request_accessors { ($self: ident, $contents: expr) => { @@ -863,12 +823,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 +906,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 +952,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 +973,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 +990,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 +1035,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 +1046,36 @@ tlv_stream!(InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef, INVOICE_REQUEST (90, paths: (Vec, 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 = + 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 +1084,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> for UnsignedInvoiceRequest { @@ -1106,17 +1114,19 @@ impl TryFrom> for UnsignedInvoiceRequest { fn try_from(bytes: Vec) -> Result { let invoice_request = ParsedMessage::::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 mut offset = 0; + for tlv_record in TlvStream::new(&bytes).range(0..INVOICE_REQUEST_TYPES.end) { + offset = tlv_record.end; + } + + let experimental_bytes = bytes.split_off(offset); + + Ok(UnsignedInvoiceRequest { bytes, experimental_bytes, contents, tagged_hash }) } } @@ -1129,9 +1139,14 @@ impl TryFrom> 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 +1170,18 @@ impl TryFrom 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 +1208,8 @@ impl TryFrom 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 +1264,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 +1278,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::{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, @@ -1275,51 +1297,24 @@ mod tests { #[test] fn builds_invoice_request_with_defaults() { - let unsigned_invoice_request = OfferBuilder::new(recipient_pubkey()) + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + let encrypted_payment_id = expanded_key.crypt_for_offer(payment_id.0, nonce); + + let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap(); - #[cfg(c_bindings)] - let mut unsigned_invoice_request = unsigned_invoice_request; - - let mut buffer = Vec::new(); - unsigned_invoice_request.write(&mut buffer).unwrap(); - - assert_eq!(unsigned_invoice_request.bytes, buffer.as_slice()); - assert_eq!(unsigned_invoice_request.payer_metadata(), &[1; 32]); - assert_eq!(unsigned_invoice_request.chains(), vec![ChainHash::using_genesis_block(Network::Bitcoin)]); - assert_eq!(unsigned_invoice_request.metadata(), None); - assert_eq!(unsigned_invoice_request.amount(), Some(Amount::Bitcoin { amount_msats: 1000 })); - assert_eq!(unsigned_invoice_request.description(), Some(PrintableString(""))); - assert_eq!(unsigned_invoice_request.offer_features(), &OfferFeatures::empty()); - assert_eq!(unsigned_invoice_request.absolute_expiry(), None); - assert_eq!(unsigned_invoice_request.paths(), &[]); - assert_eq!(unsigned_invoice_request.issuer(), None); - assert_eq!(unsigned_invoice_request.supported_quantity(), Quantity::One); - assert_eq!(unsigned_invoice_request.issuer_signing_pubkey(), Some(recipient_pubkey())); - assert_eq!(unsigned_invoice_request.chain(), ChainHash::using_genesis_block(Network::Bitcoin)); - assert_eq!(unsigned_invoice_request.amount_msats(), None); - assert_eq!(unsigned_invoice_request.invoice_request_features(), &InvoiceRequestFeatures::empty()); - assert_eq!(unsigned_invoice_request.quantity(), None); - assert_eq!(unsigned_invoice_request.payer_signing_pubkey(), payer_pubkey()); - assert_eq!(unsigned_invoice_request.payer_note(), None); - - match UnsignedInvoiceRequest::try_from(buffer) { - Err(e) => panic!("error parsing unsigned invoice request: {:?}", e), - Ok(parsed) => { - assert_eq!(parsed.bytes, unsigned_invoice_request.bytes); - assert_eq!(parsed.tagged_hash, unsigned_invoice_request.tagged_hash); - }, - } - - let invoice_request = unsigned_invoice_request.sign(payer_sign).unwrap(); + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap(); let mut buffer = Vec::new(); invoice_request.write(&mut buffer).unwrap(); assert_eq!(invoice_request.bytes, buffer.as_slice()); - assert_eq!(invoice_request.payer_metadata(), &[1; 32]); + assert_eq!(invoice_request.payer_metadata(), &encrypted_payment_id); assert_eq!(invoice_request.chains(), vec![ChainHash::using_genesis_block(Network::Bitcoin)]); assert_eq!(invoice_request.metadata(), None); assert_eq!(invoice_request.amount(), Some(Amount::Bitcoin { amount_msats: 1000 })); @@ -1334,16 +1329,19 @@ mod tests { assert_eq!(invoice_request.amount_msats(), None); assert_eq!(invoice_request.invoice_request_features(), &InvoiceRequestFeatures::empty()); assert_eq!(invoice_request.quantity(), None); - assert_eq!(invoice_request.payer_signing_pubkey(), payer_pubkey()); assert_eq!(invoice_request.payer_note(), None); let message = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &invoice_request.bytes); - assert!(merkle::verify_signature(&invoice_request.signature, &message, payer_pubkey()).is_ok()); + assert!( + merkle::verify_signature( + &invoice_request.signature, &message, invoice_request.payer_signing_pubkey(), + ).is_ok() + ); assert_eq!( invoice_request.as_tlv_stream(), ( - PayerTlvStreamRef { metadata: Some(&vec![1; 32]) }, + PayerTlvStreamRef { metadata: Some(&encrypted_payment_id.to_vec()) }, OfferTlvStreamRef { chains: None, metadata: None, @@ -1362,11 +1360,17 @@ mod tests { amount: None, features: None, quantity: None, - payer_id: Some(&payer_pubkey()), + payer_id: Some(&invoice_request.payer_signing_pubkey()), payer_note: None, paths: None, }, SignatureTlvStreamRef { signature: Some(&invoice_request.signature()) }, + ExperimentalOfferTlvStreamRef { + experimental_foo: None, + }, + ExperimentalInvoiceRequestTlvStreamRef { + experimental_bar: None, + }, ), ); @@ -1378,6 +1382,12 @@ mod tests { #[cfg(feature = "std")] #[test] fn builds_invoice_request_from_offer_with_expiration() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + let future_expiry = Duration::from_secs(u64::max_value()); let past_expiry = Duration::from_secs(0); @@ -1385,8 +1395,8 @@ mod tests { .amount_msats(1000) .absolute_expiry(future_expiry) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign() { panic!("error building invoice_request: {:?}", e); } @@ -1395,92 +1405,14 @@ mod tests { .amount_msats(1000) .absolute_expiry(past_expiry) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign() { Ok(_) => panic!("expected error"), Err(e) => assert_eq!(e, Bolt12SemanticError::AlreadyExpired), } } - #[test] - fn builds_invoice_request_with_derived_metadata() { - let signing_pubkey = payer_pubkey(); - let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); - let entropy = FixedEntropy {}; - let nonce = Nonce::from_entropy_source(&entropy); - let secp_ctx = Secp256k1::new(); - let payment_id = PaymentId([1; 32]); - - let offer = OfferBuilder::new(recipient_pubkey()) - .amount_msats(1000) - .build().unwrap(); - let invoice_request = offer - .request_invoice_deriving_metadata(signing_pubkey, &expanded_key, nonce, payment_id) - .unwrap() - .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() - .build().unwrap() - .sign(recipient_sign).unwrap(); - match invoice.verify_using_metadata(&expanded_key, &secp_ctx) { - Ok(payment_id) => assert_eq!(payment_id, PaymentId([1; 32])), - Err(()) => panic!("verification failed"), - } - assert!( - invoice.verify_using_payer_data(payment_id, nonce, &expanded_key, &secp_ctx).is_err() - ); - - // 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 - ) = 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 mut bytes = Vec::new(); - 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 invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap(); - assert!(invoice.verify_using_metadata(&expanded_key, &secp_ctx).is_err()); - - // Fails verification with altered metadata - let ( - mut payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream, - mut signature_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 mut bytes = Vec::new(); - 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 invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap(); - assert!(invoice.verify_using_metadata(&expanded_key, &secp_ctx).is_err()); - } - #[test] fn builds_invoice_request_with_derived_payer_signing_pubkey() { let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); @@ -1491,15 +1423,17 @@ 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() + .request_invoice(&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 +1444,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 +1476,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!( @@ -1560,17 +1508,22 @@ mod tests { #[test] fn builds_invoice_request_with_chain() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + let mainnet = ChainHash::using_genesis_block(Network::Bitcoin); let testnet = ChainHash::using_genesis_block(Network::Testnet); let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() .chain(Network::Bitcoin).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap(); - let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + .build_and_sign().unwrap(); + let (_, _, tlv_stream, _, _, _) = invoice_request.as_tlv_stream(); assert_eq!(invoice_request.chain(), mainnet); assert_eq!(tlv_stream.chain, None); @@ -1578,11 +1531,10 @@ mod tests { .amount_msats(1000) .chain(Network::Testnet) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() .chain(Network::Testnet).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap(); - let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + .build_and_sign().unwrap(); + let (_, _, tlv_stream, _, _, _) = invoice_request.as_tlv_stream(); assert_eq!(invoice_request.chain(), testnet); assert_eq!(tlv_stream.chain, Some(&testnet)); @@ -1591,11 +1543,10 @@ mod tests { .chain(Network::Bitcoin) .chain(Network::Testnet) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() .chain(Network::Bitcoin).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap(); - let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + .build_and_sign().unwrap(); + let (_, _, tlv_stream, _, _, _) = invoice_request.as_tlv_stream(); assert_eq!(invoice_request.chain(), mainnet); assert_eq!(tlv_stream.chain, None); @@ -1604,12 +1555,11 @@ mod tests { .chain(Network::Bitcoin) .chain(Network::Testnet) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() .chain(Network::Bitcoin).unwrap() .chain(Network::Testnet).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap(); - let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + .build_and_sign().unwrap(); + let (_, _, tlv_stream, _, _, _) = invoice_request.as_tlv_stream(); assert_eq!(invoice_request.chain(), testnet); assert_eq!(tlv_stream.chain, Some(&testnet)); @@ -1617,7 +1567,7 @@ mod tests { .amount_msats(1000) .chain(Network::Testnet) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() .chain(Network::Bitcoin) { Ok(_) => panic!("expected error"), @@ -1628,8 +1578,8 @@ mod tests { .amount_msats(1000) .chain(Network::Testnet) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign() { Ok(_) => panic!("expected error"), Err(e) => assert_eq!(e, Bolt12SemanticError::UnsupportedChain), @@ -1638,44 +1588,47 @@ mod tests { #[test] fn builds_invoice_request_with_amount() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() .amount_msats(1000).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap(); - let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + .build_and_sign().unwrap(); + let (_, _, tlv_stream, _, _, _) = invoice_request.as_tlv_stream(); assert_eq!(invoice_request.amount_msats(), Some(1000)); assert_eq!(tlv_stream.amount, Some(1000)); let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() .amount_msats(1001).unwrap() .amount_msats(1000).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap(); - let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + .build_and_sign().unwrap(); + let (_, _, tlv_stream, _, _, _) = invoice_request.as_tlv_stream(); assert_eq!(invoice_request.amount_msats(), Some(1000)); assert_eq!(tlv_stream.amount, Some(1000)); let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() .amount_msats(1001).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap(); - let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + .build_and_sign().unwrap(); + let (_, _, tlv_stream, _, _, _) = invoice_request.as_tlv_stream(); assert_eq!(invoice_request.amount_msats(), Some(1001)); assert_eq!(tlv_stream.amount, Some(1001)); match OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() .amount_msats(999) { Ok(_) => panic!("expected error"), @@ -1686,7 +1639,7 @@ mod tests { .amount_msats(1000) .supported_quantity(Quantity::Unbounded) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() .quantity(2).unwrap() .amount_msats(1000) { @@ -1697,7 +1650,7 @@ mod tests { match OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() .amount_msats(MAX_VALUE_MSAT + 1) { Ok(_) => panic!("expected error"), @@ -1708,10 +1661,10 @@ mod tests { .amount_msats(1000) .supported_quantity(Quantity::Unbounded) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() .amount_msats(1000).unwrap() .quantity(2).unwrap() - .build() + .build_and_sign() { Ok(_) => panic!("expected error"), Err(e) => assert_eq!(e, Bolt12SemanticError::InsufficientAmount), @@ -1719,8 +1672,8 @@ mod tests { match OfferBuilder::new(recipient_pubkey()) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign() { Ok(_) => panic!("expected error"), Err(e) => assert_eq!(e, Bolt12SemanticError::MissingAmount), @@ -1730,9 +1683,9 @@ mod tests { .amount_msats(1000) .supported_quantity(Quantity::Unbounded) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() .quantity(u64::max_value()).unwrap() - .build() + .build_and_sign() { Ok(_) => panic!("expected error"), Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidAmount), @@ -1741,32 +1694,42 @@ mod tests { #[test] fn builds_invoice_request_with_features() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() .features_unchecked(InvoiceRequestFeatures::unknown()) - .build().unwrap() - .sign(payer_sign).unwrap(); - let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + .build_and_sign().unwrap(); + 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())); let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() .features_unchecked(InvoiceRequestFeatures::unknown()) .features_unchecked(InvoiceRequestFeatures::empty()) - .build().unwrap() - .sign(payer_sign).unwrap(); - let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + .build_and_sign().unwrap(); + let (_, _, tlv_stream, _, _, _) = invoice_request.as_tlv_stream(); assert_eq!(invoice_request.invoice_request_features(), &InvoiceRequestFeatures::empty()); assert_eq!(tlv_stream.features, None); } #[test] fn builds_invoice_request_with_quantity() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + let one = NonZeroU64::new(1).unwrap(); let ten = NonZeroU64::new(10).unwrap(); @@ -1774,10 +1737,9 @@ mod tests { .amount_msats(1000) .supported_quantity(Quantity::One) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap(); - let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap(); + let (_, _, tlv_stream, _, _, _) = invoice_request.as_tlv_stream(); assert_eq!(invoice_request.quantity(), None); assert_eq!(tlv_stream.quantity, None); @@ -1785,7 +1747,7 @@ mod tests { .amount_msats(1000) .supported_quantity(Quantity::One) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() .amount_msats(2_000).unwrap() .quantity(2) { @@ -1797,12 +1759,11 @@ mod tests { .amount_msats(1000) .supported_quantity(Quantity::Bounded(ten)) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() .amount_msats(10_000).unwrap() .quantity(10).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap(); - let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + .build_and_sign().unwrap(); + 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)); @@ -1810,7 +1771,7 @@ mod tests { .amount_msats(1000) .supported_quantity(Quantity::Bounded(ten)) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() .amount_msats(11_000).unwrap() .quantity(11) { @@ -1822,12 +1783,11 @@ mod tests { .amount_msats(1000) .supported_quantity(Quantity::Unbounded) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() .amount_msats(2_000).unwrap() .quantity(2).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap(); - let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + .build_and_sign().unwrap(); + 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)); @@ -1835,8 +1795,8 @@ mod tests { .amount_msats(1000) .supported_quantity(Quantity::Unbounded) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign() { Ok(_) => panic!("expected error"), Err(e) => assert_eq!(e, Bolt12SemanticError::MissingQuantity), @@ -1846,8 +1806,8 @@ mod tests { .amount_msats(1000) .supported_quantity(Quantity::Bounded(one)) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign() { Ok(_) => panic!("expected error"), Err(e) => assert_eq!(e, Bolt12SemanticError::MissingQuantity), @@ -1856,64 +1816,48 @@ mod tests { #[test] fn builds_invoice_request_with_payer_note() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() .payer_note("bar".into()) - .build().unwrap() - .sign(payer_sign).unwrap(); - let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + .build_and_sign().unwrap(); + 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"))); let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() .payer_note("bar".into()) .payer_note("baz".into()) - .build().unwrap() - .sign(payer_sign).unwrap(); - let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + .build_and_sign().unwrap(); + 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"))); } - #[test] - fn fails_signing_invoice_request() { - match OfferBuilder::new(recipient_pubkey()) - .amount_msats(1000) - .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(fail_sign) - { - Ok(_) => panic!("expected error"), - Err(e) => assert_eq!(e, SignError::Signing), - } - - match OfferBuilder::new(recipient_pubkey()) - .amount_msats(1000) - .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(recipient_sign) - { - Ok(_) => panic!("expected error"), - Err(e) => assert_eq!(e, SignError::Verification(secp256k1::Error::IncorrectSignature)), - } - } - #[test] fn fails_responding_with_unknown_required_features() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + match OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![42; 32], payer_pubkey()).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() .features_unchecked(InvoiceRequestFeatures::unknown()) - .build().unwrap() - .sign(payer_sign).unwrap() + .build_and_sign().unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()) { Ok(_) => panic!("expected error"), @@ -1923,12 +1867,17 @@ mod tests { #[test] fn parses_invoice_request_with_metadata() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap(); + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap(); let mut buffer = Vec::new(); invoice_request.write(&mut buffer).unwrap(); @@ -1940,13 +1889,18 @@ mod tests { #[test] fn parses_invoice_request_with_chain() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() .chain(Network::Bitcoin).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap(); + .build_and_sign().unwrap(); let mut buffer = Vec::new(); invoice_request.write(&mut buffer).unwrap(); @@ -1958,10 +1912,9 @@ mod tests { let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() .chain_unchecked(Network::Testnet) - .build_unchecked() - .sign(payer_sign).unwrap(); + .build_unchecked_and_sign(); let mut buffer = Vec::new(); invoice_request.write(&mut buffer).unwrap(); @@ -1974,12 +1927,17 @@ mod tests { #[test] fn parses_invoice_request_with_amount() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap(); + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap(); let mut buffer = Vec::new(); invoice_request.write(&mut buffer).unwrap(); @@ -1990,10 +1948,9 @@ mod tests { let invoice_request = OfferBuilder::new(recipient_pubkey()) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() .amount_msats(1000).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap(); + .build_and_sign().unwrap(); let mut buffer = Vec::new(); invoice_request.write(&mut buffer).unwrap(); @@ -2004,9 +1961,8 @@ mod tests { let invoice_request = OfferBuilder::new(recipient_pubkey()) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build_unchecked() - .sign(payer_sign).unwrap(); + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_unchecked_and_sign(); let mut buffer = Vec::new(); invoice_request.write(&mut buffer).unwrap(); @@ -2019,10 +1975,9 @@ mod tests { let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() .amount_msats_unchecked(999) - .build_unchecked() - .sign(payer_sign).unwrap(); + .build_unchecked_and_sign(); let mut buffer = Vec::new(); invoice_request.write(&mut buffer).unwrap(); @@ -2036,9 +1991,8 @@ mod tests { .description("foo".to_string()) .amount(Amount::Currency { iso4217_code: *b"USD", amount: 1000 }) .build_unchecked() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build_unchecked() - .sign(payer_sign).unwrap(); + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_unchecked_and_sign(); let mut buffer = Vec::new(); invoice_request.write(&mut buffer).unwrap(); @@ -2054,10 +2008,9 @@ mod tests { .amount_msats(1000) .supported_quantity(Quantity::Unbounded) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() .quantity(u64::max_value()).unwrap() - .build_unchecked() - .sign(payer_sign).unwrap(); + .build_unchecked_and_sign(); let mut buffer = Vec::new(); invoice_request.write(&mut buffer).unwrap(); @@ -2070,6 +2023,12 @@ mod tests { #[test] fn parses_invoice_request_with_quantity() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + let one = NonZeroU64::new(1).unwrap(); let ten = NonZeroU64::new(10).unwrap(); @@ -2077,9 +2036,8 @@ mod tests { .amount_msats(1000) .supported_quantity(Quantity::One) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap(); + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap(); let mut buffer = Vec::new(); invoice_request.write(&mut buffer).unwrap(); @@ -2092,11 +2050,10 @@ mod tests { .amount_msats(1000) .supported_quantity(Quantity::One) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() .amount_msats(2_000).unwrap() .quantity_unchecked(2) - .build_unchecked() - .sign(payer_sign).unwrap(); + .build_unchecked_and_sign(); let mut buffer = Vec::new(); invoice_request.write(&mut buffer).unwrap(); @@ -2112,11 +2069,10 @@ mod tests { .amount_msats(1000) .supported_quantity(Quantity::Bounded(ten)) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() .amount_msats(10_000).unwrap() .quantity(10).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap(); + .build_and_sign().unwrap(); let mut buffer = Vec::new(); invoice_request.write(&mut buffer).unwrap(); @@ -2129,11 +2085,10 @@ mod tests { .amount_msats(1000) .supported_quantity(Quantity::Bounded(ten)) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() .amount_msats(11_000).unwrap() .quantity_unchecked(11) - .build_unchecked() - .sign(payer_sign).unwrap(); + .build_unchecked_and_sign(); let mut buffer = Vec::new(); invoice_request.write(&mut buffer).unwrap(); @@ -2147,11 +2102,10 @@ mod tests { .amount_msats(1000) .supported_quantity(Quantity::Unbounded) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() .amount_msats(2_000).unwrap() .quantity(2).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap(); + .build_and_sign().unwrap(); let mut buffer = Vec::new(); invoice_request.write(&mut buffer).unwrap(); @@ -2164,9 +2118,8 @@ mod tests { .amount_msats(1000) .supported_quantity(Quantity::Unbounded) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build_unchecked() - .sign(payer_sign).unwrap(); + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_unchecked_and_sign(); let mut buffer = Vec::new(); invoice_request.write(&mut buffer).unwrap(); @@ -2180,9 +2133,8 @@ mod tests { .amount_msats(1000) .supported_quantity(Quantity::Bounded(one)) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build_unchecked() - .sign(payer_sign).unwrap(); + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_unchecked_and_sign(); let mut buffer = Vec::new(); invoice_request.write(&mut buffer).unwrap(); @@ -2195,11 +2147,17 @@ mod tests { #[test] fn fails_parsing_invoice_request_without_metadata() { - let offer = OfferBuilder::new(recipient_pubkey()) + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + + let unsigned_invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) - .build().unwrap(); - let unsigned_invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap(); + .build().unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_unchecked(); let mut tlv_stream = unsigned_invoice_request.contents.as_tlv_stream(); tlv_stream.0.metadata = None; @@ -2216,11 +2174,17 @@ mod tests { #[test] fn fails_parsing_invoice_request_without_payer_signing_pubkey() { - let offer = OfferBuilder::new(recipient_pubkey()) + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + + let unsigned_invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) - .build().unwrap(); - let unsigned_invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap(); + .build().unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_unchecked(); let mut tlv_stream = unsigned_invoice_request.contents.as_tlv_stream(); tlv_stream.2.payer_id = None; @@ -2235,11 +2199,17 @@ mod tests { #[test] fn fails_parsing_invoice_request_without_issuer_id() { - let offer = OfferBuilder::new(recipient_pubkey()) + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + + let unsigned_invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) - .build().unwrap(); - let unsigned_invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap(); + .build().unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_unchecked(); let mut tlv_stream = unsigned_invoice_request.contents.as_tlv_stream(); tlv_stream.1.issuer_id = None; @@ -2256,12 +2226,18 @@ mod tests { #[test] fn fails_parsing_invoice_request_without_signature() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + let mut buffer = Vec::new(); OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_unchecked() .contents .write(&mut buffer).unwrap(); @@ -2273,12 +2249,17 @@ mod tests { #[test] fn fails_parsing_invoice_request_with_invalid_signature() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + let mut invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap(); + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap(); let last_signature_byte = invoice_request.bytes.last_mut().unwrap(); *last_signature_byte = last_signature_byte.wrapping_add(1); @@ -2294,19 +2275,165 @@ mod tests { } #[test] - fn fails_parsing_invoice_request_with_extra_tlv_records() { + fn parses_invoice_request_with_unknown_tlv_records() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let payment_id = PaymentId([1; 32]); + + 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, payer_keys, _) = OfferBuilder::new(keys.public_key()) + .amount_msats(1000) + .build().unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_without_checks(); + + BigSize(UNKNOWN_ODD_TYPE).write(&mut unsigned_invoice_request.bytes).unwrap(); + BigSize(32).write(&mut unsigned_invoice_request.bytes).unwrap(); + [42u8; 32].write(&mut unsigned_invoice_request.bytes).unwrap(); + + unsigned_invoice_request.tagged_hash = + TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &unsigned_invoice_request.bytes); + + let keys = payer_keys.unwrap(); + 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(); + + if let Err(e) = InvoiceRequest::try_from(encoded_invoice_request) { + 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, payer_keys, _) = OfferBuilder::new(keys.public_key()) + .amount_msats(1000) + .build().unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_without_checks(); + + BigSize(UNKNOWN_EVEN_TYPE).write(&mut unsigned_invoice_request.bytes).unwrap(); + BigSize(32).write(&mut unsigned_invoice_request.bytes).unwrap(); + [42u8; 32].write(&mut unsigned_invoice_request.bytes).unwrap(); + + unsigned_invoice_request.tagged_hash = + TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &unsigned_invoice_request.bytes); + + let keys = payer_keys.unwrap(); + 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() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let payment_id = PaymentId([1; 32]); + + 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 invoice_request = OfferBuilder::new(keys.public_key()) + let (mut unsigned_invoice_request, payer_keys, _) = OfferBuilder::new(keys.public_key()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], keys.public_key()).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_without_checks(); + + BigSize(UNKNOWN_ODD_TYPE).write(&mut unsigned_invoice_request.experimental_bytes).unwrap(); + BigSize(32).write(&mut unsigned_invoice_request.experimental_bytes).unwrap(); + [42u8; 32].write(&mut unsigned_invoice_request.experimental_bytes).unwrap(); + + 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 keys = payer_keys.unwrap(); + 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(); + + if let Err(e) = InvoiceRequest::try_from(encoded_invoice_request) { + 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, payer_keys, _) = OfferBuilder::new(keys.public_key()) + .amount_msats(1000) .build().unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_without_checks(); + + BigSize(UNKNOWN_EVEN_TYPE).write(&mut unsigned_invoice_request.experimental_bytes).unwrap(); + BigSize(32).write(&mut unsigned_invoice_request.experimental_bytes).unwrap(); + [42u8; 32].write(&mut unsigned_invoice_request.experimental_bytes).unwrap(); + + 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 keys = payer_keys.unwrap(); + 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 fails_parsing_invoice_request_with_out_of_range_tlv_records() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + + let invoice_request = OfferBuilder::new(recipient_pubkey()) + .amount_msats(1000) + .build().unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap(); + let mut encoded_invoice_request = Vec::new(); invoice_request.write(&mut encoded_invoice_request).unwrap(); BigSize(1002).write(&mut encoded_invoice_request).unwrap(); @@ -2317,6 +2444,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] @@ -2326,6 +2464,7 @@ mod tests { let entropy = FixedEntropy {}; let nonce = Nonce::from_entropy_source(&entropy); let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); #[cfg(c_bindings)] use crate::offers::offer::OfferWithDerivedMetadataBuilder as OfferBuilder; @@ -2336,12 +2475,12 @@ mod tests { .build().unwrap(); assert_eq!(offer.issuer_signing_pubkey(), Some(node_id)); - let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap() + let invoice_request = offer + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() .chain(Network::Testnet).unwrap() .quantity(1).unwrap() .payer_note("0".repeat(PAYER_NOTE_LIMIT * 2)) - .build().unwrap() - .sign(payer_sign).unwrap(); + .build_and_sign().unwrap(); match invoice_request.verify_using_metadata(&expanded_key, &secp_ctx) { Ok(invoice_request) => { let fields = invoice_request.fields(); @@ -2349,7 +2488,7 @@ mod tests { assert_eq!( fields, InvoiceRequestFields { - payer_signing_pubkey: payer_pubkey(), + payer_signing_pubkey: invoice_request.payer_signing_pubkey(), quantity: Some(1), payer_note_truncated: Some(UntrustedString("0".repeat(PAYER_NOTE_LIMIT))), } diff --git a/lightning/src/offers/merkle.rs b/lightning/src/offers/merkle.rs index e2fed2e800b..c3fa3be1057 100644 --- a/lightning/src/offers/merkle.rs +++ b/lightning/src/offers/merkle.rs @@ -21,7 +21,7 @@ use crate::prelude::*; /// Valid type range for signature TLV records. const SIGNATURE_TYPES: core::ops::RangeInclusive = 240..=1000; -tlv_stream!(SignatureTlvStream, SignatureTlvStreamRef, SIGNATURE_TYPES, { +tlv_stream!(SignatureTlvStream, SignatureTlvStreamRef<'a>, SIGNATURE_TYPES, { (240, signature: Signature), }); @@ -164,7 +164,7 @@ fn root_hash<'a, I: core::iter::Iterator>>(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 +240,15 @@ 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> - ) -> impl core::iter::Iterator> { - 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) end: usize, } impl<'a> Iterator for TlvStream<'a> { @@ -277,41 +271,37 @@ 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, 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(&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; use bitcoin::secp256k1::{Keypair, Message, Secp256k1, SecretKey}; use bitcoin::secp256k1::schnorr::Signature; + use crate::ln::channelmanager::PaymentId; + use crate::ln::inbound_payment::ExpandedKey; + use crate::offers::nonce::Nonce; use crate::offers::offer::{Amount, OfferBuilder}; use crate::offers::invoice_request::{InvoiceRequest, UnsignedInvoiceRequest}; use crate::offers::parse::Bech32Encode; - use crate::offers::test_utils::{payer_pubkey, recipient_pubkey}; + use crate::offers::signer::Metadata; + use crate::offers::test_utils::recipient_pubkey; + use crate::sign::KeyMaterial; use crate::util::ser::Writeable; #[test] @@ -336,7 +326,11 @@ mod tests { #[test] fn calculates_merkle_root_hash_from_invoice_request() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let nonce = Nonce([0u8; 16]); let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + let recipient_pubkey = { let secret_key = SecretKey::from_slice(&>::from_hex("4141414141414141414141414141414141414141414141414141414141414141").unwrap()).unwrap(); Keypair::from_secret_key(&secp_ctx, &secret_key).public_key() @@ -351,7 +345,10 @@ mod tests { .description("A Mathematical Treatise".into()) .amount(Amount::Currency { iso4217_code: *b"USD", amount: 100 }) .build_unchecked() - .request_invoice(vec![0; 8], payer_keys.public_key()).unwrap() + // Override the payer metadata and signing pubkey to match the test vectors + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .payer_metadata(Metadata::Bytes(vec![0; 8])) + .payer_signing_pubkey(payer_keys.public_key()) .build_unchecked() .sign(|message: &UnsignedInvoiceRequest| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &payer_keys)) @@ -371,48 +368,53 @@ mod tests { ); } - #[test] - fn compute_tagged_hash() { - let unsigned_invoice_request = OfferBuilder::new(recipient_pubkey()) - .amount_msats(1000) - .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .payer_note("bar".into()) - .build().unwrap(); - - // Simply test that we can grab the tag and merkle root exposed by the accessor - // functions, then use them to succesfully compute a tagged hash. - let tagged_hash = unsigned_invoice_request.as_ref(); - let expected_digest = unsigned_invoice_request.as_ref().as_digest(); - let tag = sha256::Hash::hash(tagged_hash.tag().as_bytes()); - let actual_digest = Message::from_digest(super::tagged_hash(tag, tagged_hash.merkle_root()).to_byte_array()); - assert_eq!(*expected_digest, actual_digest); - } + #[test] + fn compute_tagged_hash() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let nonce = Nonce([0u8; 16]); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + + let unsigned_invoice_request = OfferBuilder::new(recipient_pubkey()) + .amount_msats(1000) + .build().unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .payer_note("bar".into()) + .build_unchecked(); + + // Simply test that we can grab the tag and merkle root exposed by the accessor + // functions, then use them to succesfully compute a tagged hash. + let tagged_hash = unsigned_invoice_request.as_ref(); + let expected_digest = unsigned_invoice_request.as_ref().as_digest(); + let tag = sha256::Hash::hash(tagged_hash.tag().as_bytes()); + let actual_digest = Message::from_digest(super::tagged_hash(tag, tagged_hash.merkle_root()).to_byte_array()); + assert_eq!(*expected_digest, actual_digest); + } #[test] fn skips_encoding_signature_tlv_records() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let nonce = Nonce([0u8; 16]); let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + let recipient_pubkey = { let secret_key = SecretKey::from_slice(&[41; 32]).unwrap(); Keypair::from_secret_key(&secp_ctx, &secret_key).public_key() }; - let payer_keys = { - let secret_key = SecretKey::from_slice(&[42; 32]).unwrap(); - Keypair::from_secret_key(&secp_ctx, &secret_key) - }; let invoice_request = OfferBuilder::new(recipient_pubkey) .amount_msats(100) .build_unchecked() - .request_invoice(vec![0; 8], payer_keys.public_key()).unwrap() - .build_unchecked() - .sign(|message: &UnsignedInvoiceRequest| - Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &payer_keys)) - ) - .unwrap(); + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().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!( @@ -423,24 +425,21 @@ mod tests { #[test] fn iterates_over_tlv_stream_range() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let nonce = Nonce([0u8; 16]); let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + let recipient_pubkey = { let secret_key = SecretKey::from_slice(&[41; 32]).unwrap(); Keypair::from_secret_key(&secp_ctx, &secret_key).public_key() }; - let payer_keys = { - let secret_key = SecretKey::from_slice(&[42; 32]).unwrap(); - Keypair::from_secret_key(&secp_ctx, &secret_key) - }; let invoice_request = OfferBuilder::new(recipient_pubkey) .amount_msats(100) .build_unchecked() - .request_invoice(vec![0; 8], payer_keys.public_key()).unwrap() - .build_unchecked() - .sign(|message: &UnsignedInvoiceRequest| - Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &payer_keys)) - ) + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign() .unwrap(); let tlv_stream = TlvStream::new(&invoice_request.bytes).range(0..1) diff --git a/lightning/src/offers/offer.rs b/lightning/src/offers/offer.rs index 8501b8d4651..0305f422423 100644 --- a/lightning/src/offers/offer.rs +++ b/lightning/src/offers/offer.rs @@ -90,20 +90,20 @@ use crate::ln::channelmanager::PaymentId; use crate::ln::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))] use { - crate::offers::invoice_request::{DerivedPayerSigningPubkey, ExplicitPayerSigningPubkey, InvoiceRequestBuilder}, + crate::offers::invoice_request::InvoiceRequestBuilder, }; #[cfg(c_bindings)] use { - crate::offers::invoice_request::{InvoiceRequestWithDerivedPayerSigningPubkeyBuilder, InvoiceRequestWithExplicitPayerSigningPubkeyBuilder}, + crate::offers::invoice_request::InvoiceRequestWithDerivedPayerSigningPubkeyBuilder, }; #[allow(unused_imports)] @@ -128,7 +128,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()) } @@ -225,6 +225,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, @@ -266,6 +268,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), @@ -400,10 +404,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 @@ -464,6 +468,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() @@ -571,6 +581,8 @@ pub(super) struct OfferContents { paths: Option>, supported_quantity: Quantity, issuer_signing_pubkey: Option, + #[cfg(test)] + experimental_foo: Option, } macro_rules! offer_accessors { ($self: ident, $contents: expr) => { @@ -687,6 +699,13 @@ impl Offer { self.contents.expects_quantity() } + pub(super) fn tlv_stream_iter<'a>( + bytes: &'a [u8] + ) -> impl core::iter::Iterator> { + TlvStream::new(bytes).range(OFFER_TYPES) + .chain(TlvStream::new(bytes).range(EXPERIMENTAL_OFFER_TYPES)) + } + #[cfg(async_payments)] pub(super) fn verify( &self, nonce: Nonce, key: &ExpandedKey, secp_ctx: &Secp256k1 @@ -696,23 +715,23 @@ impl Offer { } macro_rules! request_invoice_derived_signing_pubkey { ($self: ident, $builder: ty) => { - /// Similar to [`Offer::request_invoice`] except it: + /// Creates an [`InvoiceRequestBuilder`] for the offer, which /// - derives the [`InvoiceRequest::payer_signing_pubkey`] such that a different key can be used - /// for each request, - /// - sets [`InvoiceRequest::payer_metadata`] when [`InvoiceRequestBuilder::build`] is called - /// such that it can be used by [`Bolt12Invoice::verify_using_metadata`] to determine if the - /// invoice was requested using a base [`ExpandedKey`] from which the payer id was derived, - /// and + /// for each request in order to protect the sender's privacy, + /// - sets [`InvoiceRequest::payer_metadata`] when [`InvoiceRequestBuilder::build_and_sign`] is + /// called such that it can be used by [`Bolt12Invoice::verify_using_metadata`] to determine + /// if the invoice was requested using a base [`ExpandedKey`] from which the payer id was + /// derived, and /// - includes the [`PaymentId`] encrypted in [`InvoiceRequest::payer_metadata`] so that it can /// be used when sending the payment for the requested invoice. /// - /// Useful to protect the sender's privacy. + /// Errors if the offer contains unknown required features. /// /// [`InvoiceRequest::payer_signing_pubkey`]: crate::offers::invoice_request::InvoiceRequest::payer_signing_pubkey /// [`InvoiceRequest::payer_metadata`]: crate::offers::invoice_request::InvoiceRequest::payer_metadata /// [`Bolt12Invoice::verify_using_metadata`]: crate::offers::invoice::Bolt12Invoice::verify_using_metadata /// [`ExpandedKey`]: crate::ln::inbound_payment::ExpandedKey - pub fn request_invoice_deriving_signing_pubkey< + pub fn request_invoice< 'a, 'b, #[cfg(not(c_bindings))] T: secp256k1::Signing @@ -732,64 +751,19 @@ macro_rules! request_invoice_derived_signing_pubkey { ($self: ident, $builder: t } } } -macro_rules! request_invoice_explicit_signing_pubkey { ($self: ident, $builder: ty) => { - /// Similar to [`Offer::request_invoice_deriving_signing_pubkey`] except uses `signing_pubkey` - /// for the [`InvoiceRequest::payer_signing_pubkey`] instead of deriving a different key for - /// each request. - /// - /// Useful for recurring payments using the same `signing_pubkey` with different invoices. - /// - /// [`InvoiceRequest::payer_signing_pubkey`]: crate::offers::invoice_request::InvoiceRequest::payer_signing_pubkey - pub fn request_invoice_deriving_metadata( - &$self, signing_pubkey: PublicKey, expanded_key: &ExpandedKey, nonce: Nonce, - payment_id: PaymentId - ) -> Result<$builder, Bolt12SemanticError> { - if $self.offer_features().requires_unknown_bits() { - return Err(Bolt12SemanticError::UnknownRequiredFeatures); - } - - Ok(<$builder>::deriving_metadata($self, signing_pubkey, expanded_key, nonce, payment_id)) - } - - /// Creates an [`InvoiceRequestBuilder`] for the offer with the given `metadata` and - /// `signing_pubkey`, which will be reflected in the `Bolt12Invoice` response. - /// - /// The `metadata` is useful for including information about the derivation of `signing_pubkey` - /// such that invoice response handling can be stateless. Also serves as payer-provided entropy - /// while hashing in the signature calculation. - /// - /// This should not leak any information such as by using a simple BIP-32 derivation path. - /// Otherwise, payments may be correlated. - /// - /// Errors if the offer contains unknown required features. - /// - /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest - pub fn request_invoice( - &$self, metadata: Vec, signing_pubkey: PublicKey - ) -> Result<$builder, Bolt12SemanticError> { - if $self.offer_features().requires_unknown_bits() { - return Err(Bolt12SemanticError::UnknownRequiredFeatures); - } - - Ok(<$builder>::new($self, metadata, signing_pubkey)) - } -} } - #[cfg(not(c_bindings))] impl Offer { - request_invoice_derived_signing_pubkey!(self, InvoiceRequestBuilder<'a, 'b, DerivedPayerSigningPubkey, T>); - request_invoice_explicit_signing_pubkey!(self, InvoiceRequestBuilder); + request_invoice_derived_signing_pubkey!(self, InvoiceRequestBuilder<'a, 'b, T>); } #[cfg(c_bindings)] impl Offer { request_invoice_derived_signing_pubkey!(self, InvoiceRequestWithDerivedPayerSigningPubkeyBuilder<'a, 'b>); - request_invoice_explicit_signing_pubkey!(self, InvoiceRequestWithExplicitPayerSigningPubkeyBuilder); } #[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() } } @@ -957,7 +931,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(()), @@ -974,7 +950,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)), @@ -987,7 +963,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, @@ -999,7 +975,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) } } @@ -1077,7 +1060,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, WithoutLength)), (OFFER_METADATA_TYPE, metadata: (Vec, WithoutLength)), (6, currency: CurrencyCode), @@ -1091,6 +1074,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 = 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: &mut io::Cursor) -> Result { + 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"; } @@ -1107,7 +1115,7 @@ impl TryFrom> for Offer { type Error = Bolt12ParseError; fn try_from(bytes: Vec) -> Result { - let offer = ParsedMessage::::try_from(bytes)?; + let offer = ParsedMessage::::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); @@ -1116,14 +1124,20 @@ impl TryFrom> for Offer { } } -impl TryFrom for OfferContents { +impl TryFrom for OfferContents { type Error = Bolt12SemanticError; - fn try_from(tlv_stream: OfferTlvStream) -> Result { - 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 { + 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)); @@ -1161,6 +1175,8 @@ impl TryFrom for OfferContents { Ok(OfferContents { chains, metadata, amount, description, features, absolute_expiry, issuer, paths, supported_quantity, issuer_signing_pubkey, + #[cfg(test)] + experimental_foo, }) } } @@ -1173,7 +1189,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, @@ -1191,6 +1207,7 @@ mod tests { use crate::blinded_path::BlindedHop; use crate::blinded_path::message::BlindedMessagePath; use crate::sign::KeyMaterial; + use crate::ln::channelmanager::PaymentId; use crate::ln::features::OfferFeatures; use crate::ln::inbound_payment::ExpandedKey; use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT}; @@ -1225,19 +1242,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) { @@ -1256,7 +1278,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) @@ -1264,7 +1286,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) @@ -1273,7 +1295,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) @@ -1283,7 +1305,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] @@ -1293,7 +1315,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() @@ -1301,7 +1323,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] @@ -1311,56 +1333,56 @@ mod tests { let entropy = FixedEntropy {}; let nonce = Nonce::from_entropy_source(&entropy); let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); #[cfg(c_bindings)] 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)); - let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap(); + let invoice_request = offer + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap(); match invoice_request.verify_using_metadata(&expanded_key, &secp_ctx) { Ok(invoice_request) => assert_eq!(invoice_request.offer_id, offer.id()), Err(_) => panic!("unexpected error"), } // Fails verification when using the wrong method - let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap(); + let invoice_request = offer + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap(); assert!( invoice_request.verify_using_recipient_data(nonce, &expanded_key, &secp_ctx).is_err() ); // 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(); let invoice_request = Offer::try_from(encoded_offer).unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap(); + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap(); assert!(invoice_request.verify_using_metadata(&expanded_key, &secp_ctx).is_err()); // 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(); let invoice_request = Offer::try_from(encoded_offer).unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap(); + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap(); assert!(invoice_request.verify_using_metadata(&expanded_key, &secp_ctx).is_err()); } @@ -1371,6 +1393,7 @@ mod tests { let entropy = FixedEntropy {}; let nonce = Nonce::from_entropy_source(&entropy); let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); let blinded_path = BlindedMessagePath::from_raw( pubkey(40), pubkey(41), @@ -1385,35 +1408,35 @@ 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)); - let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap(); + let invoice_request = offer + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap(); match invoice_request.verify_using_recipient_data(nonce, &expanded_key, &secp_ctx) { Ok(invoice_request) => assert_eq!(invoice_request.offer_id, offer.id()), Err(_) => panic!("unexpected error"), } // Fails verification when using the wrong method - let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap(); + let invoice_request = offer + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap(); assert!(invoice_request.verify_using_metadata(&expanded_key, &secp_ctx).is_err()); // 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(); let invoice_request = Offer::try_from(encoded_offer).unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap(); + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap(); assert!( invoice_request.verify_using_recipient_data(nonce, &expanded_key, &secp_ctx).is_err() ); @@ -1421,15 +1444,14 @@ 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(); let invoice_request = Offer::try_from(encoded_offer).unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap(); + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap(); assert!( invoice_request.verify_using_recipient_data(nonce, &expanded_key, &secp_ctx).is_err() ); @@ -1446,8 +1468,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)) @@ -1458,8 +1480,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), @@ -1471,8 +1493,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() { @@ -1488,7 +1510,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()) @@ -1496,14 +1518,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] @@ -1513,7 +1535,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()) @@ -1521,7 +1543,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] @@ -1538,7 +1560,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) @@ -1549,7 +1571,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] @@ -1580,8 +1602,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] @@ -1591,7 +1613,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()) @@ -1599,7 +1621,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] @@ -1614,7 +1636,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) @@ -1623,7 +1645,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)) @@ -1632,7 +1654,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)) @@ -1641,7 +1663,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)) @@ -1651,15 +1673,21 @@ 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] fn fails_requesting_invoice_with_unknown_required_features() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + match OfferBuilder::new(pubkey(42)) .features_unchecked(OfferFeatures::unknown()) .build().unwrap() - .request_invoice(vec![1; 32], pubkey(43)) + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id) { Ok(_) => panic!("expected error"), Err(e) => assert_eq!(e, Bolt12SemanticError::UnknownRequiredFeatures), @@ -1689,8 +1717,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(); @@ -1700,8 +1728,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(); @@ -1712,8 +1740,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(); @@ -1740,7 +1768,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(); @@ -1846,7 +1874,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(); @@ -1860,12 +1888,87 @@ 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(); + + if let Err(e) = Offer::try_from(encoded_offer) { + 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(); + + if let Err(e) = Offer::try_from(encoded_offer) { + 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(); @@ -1939,6 +2042,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::() { @@ -2081,6 +2187,18 @@ mod bolt12_tests { Err(Bolt12ParseError::Decode(DecodeError::InvalidValue)), ); + // Contains type > 1999999999 + assert_eq!( + "lno1pgz5znzfgdz3vggzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgp06ae4jsq9qgr0u2xq4dh3kdevrf4zg6hx8a60jv0gxe0ptgyfc6xkryqqqqqqqq".parse::(), + Err(Bolt12ParseError::Decode(DecodeError::InvalidValue)), + ); + + // Contains unknown even type (1000000002) + assert_eq!( + "lno1pgz5znzfgdz3vggzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgp06wu6egp9qgr0u2xq4dh3kdevrf4zg6hx8a60jv0gxe0ptgyfc6xkryqqqqqqqq".parse::(), + Err(Bolt12ParseError::Decode(DecodeError::InvalidValue)), + ); + // TODO: Resolved in spec https://github.com/lightning/bolts/pull/798/files#r1334851959 // Contains unknown feature 22 assert!( diff --git a/lightning/src/offers/payer.rs b/lightning/src/offers/payer.rs index 0ec5721dc38..696eac24044 100644 --- a/lightning/src/offers/payer.rs +++ b/lightning/src/offers/payer.rs @@ -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, WithoutLength)), }); diff --git a/lightning/src/offers/refund.rs b/lightning/src/offers/refund.rs index 482c3b6884c..c567dbd8e68 100644 --- a/lightning/src/offers/refund.rs +++ b/lightning/src/offers/refund.rs @@ -98,9 +98,9 @@ use crate::ln::channelmanager::PaymentId; use crate::ln::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, paths: Option>, + #[cfg(test)] + experimental_foo: Option, + #[cfg(test)] + experimental_bar: Option, } 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 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 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::ln::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::{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,43 @@ 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(); + + if let Err(e) = Refund::try_from(encoded_refund) { + 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 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() diff --git a/lightning/src/offers/static_invoice.rs b/lightning/src/offers/static_invoice.rs index bf88bd9446c..2c5440aa0d2 100644 --- a/lightning/src/offers/static_invoice.rs +++ b/lightning/src/offers/static_invoice.rs @@ -16,9 +16,12 @@ use crate::ln::features::{Bolt12InvoiceFeatures, OfferFeatures}; 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::{ @@ -26,7 +29,8 @@ use crate::offers::merkle::{ }; 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::util::ser::{CursorReadable, Iterable, WithoutLength, Writeable, Writer}; @@ -79,6 +83,8 @@ struct InvoiceContents { features: Bolt12InvoiceFeatures, signing_pubkey: PublicKey, message_paths: Vec, + #[cfg(test)] + experimental_baz: Option, } /// Builds a [`StaticInvoice`] from an [`Offer`]. @@ -131,9 +137,7 @@ impl<'a> StaticInvoiceBuilder<'a> { } /// Builds a signed [`StaticInvoice`] after checking for valid semantics. - pub fn build_and_sign( - self, secp_ctx: &Secp256k1, - ) -> Result { + pub fn build(self) -> Result<(UnsignedStaticInvoice, Keypair), Bolt12SemanticError> { #[cfg(feature = "std")] { if self.invoice.is_offer_expired() { @@ -149,7 +153,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( + self, secp_ctx: &Secp256k1, + ) -> Result { + 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 +170,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, + experimental_bytes: Vec, contents: InvoiceContents, tagged_hash: TaggedHash, } @@ -269,16 +284,32 @@ macro_rules! invoice_accessors_signing_pubkey { impl UnsignedStaticInvoice { fn new(offer_bytes: &Vec, 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); + const NON_EXPERIMENTAL_TYPES: core::ops::Range = OFFER_TYPES; + const EXPERIMENTAL_TYPES: core::ops::Range = EXPERIMENTAL_OFFER_TYPES; let mut bytes = Vec::new(); - unsigned_tlv_stream.write(&mut bytes).unwrap(); - 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(NON_EXPERIMENTAL_TYPES) { + record.write(&mut bytes).unwrap(); + } + + let (_, invoice_tlv_stream, _, experimental_invoice_tlv_stream) = contents.as_tlv_stream(); + invoice_tlv_stream.write(&mut bytes).unwrap(); - Self { contents, tagged_hash, bytes } + let mut experimental_bytes = Vec::new(); + + for record in TlvStream::new(offer_bytes).range(EXPERIMENTAL_TYPES) { + record.write(&mut experimental_bytes).unwrap(); + } + + experimental_invoice_tlv_stream.write(&mut experimental_bytes).unwrap(); + + 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 +323,9 @@ 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. + WithoutLength(&self.experimental_bytes).write(&mut self.bytes).unwrap(); + Ok(StaticInvoice { bytes: self.bytes, contents: self.contents, signature }) } @@ -341,11 +375,9 @@ impl StaticInvoice { } pub(crate) fn from_same_offer(&self, invreq: &InvoiceRequest) -> bool { - let invoice_offer_tlv_stream = TlvStream::new(&self.bytes) - .range(OFFER_TYPES) + let invoice_offer_tlv_stream = Offer::tlv_stream_iter(&self.bytes) .map(|tlv_record| tlv_record.record_bytes); - let invreq_offer_tlv_stream = TlvStream::new(invreq.bytes()) - .range(OFFER_TYPES) + 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 +407,8 @@ impl InvoiceContents { fallbacks: None, features: Bolt12InvoiceFeatures::empty(), signing_pubkey, + #[cfg(test)] + experimental_baz: None, } } @@ -400,7 +434,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 +538,54 @@ impl TryFrom> for StaticInvoice { } } -type FullInvoiceTlvStream = (OfferTlvStream, InvoiceTlvStream, SignatureTlvStream); +type FullInvoiceTlvStream = ( + OfferTlvStream, + InvoiceTlvStream, + SignatureTlvStream, + ExperimentalOfferTlvStream, + ExperimentalInvoiceTlvStream, +); impl CursorReadable for FullInvoiceTlvStream { fn read>(r: &mut io::Cursor) -> Result { 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> for StaticInvoice { type Error = Bolt12ParseError; fn try_from(invoice: ParsedMessage) -> Result { 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 +621,11 @@ impl TryFrom for InvoiceContents { payment_hash, amount, }, + experimental_offer_tlv_stream, + ExperimentalInvoiceTlvStream { + #[cfg(test)] + experimental_baz, + }, ) = tlv_stream; if payment_hash.is_some() { @@ -587,7 +658,7 @@ impl TryFrom 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 +666,8 @@ impl TryFrom for InvoiceContents { fallbacks, features, signing_pubkey, + #[cfg(test)] + experimental_baz, }) } } @@ -606,14 +679,20 @@ mod tests { use crate::ln::features::{Bolt12InvoiceFeatures, OfferFeatures}; 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 +702,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 { 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 +872,8 @@ mod tests { message_paths: Some(&paths), }, SignatureTlvStreamRef { signature: Some(&invoice.signature()) }, + ExperimentalOfferTlvStreamRef { experimental_foo: None }, + ExperimentalInvoiceTlvStreamRef { experimental_baz: None }, ) ); @@ -840,6 +941,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 +1034,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 +1070,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 +1332,206 @@ 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(); + + BigSize(UNKNOWN_ODD_TYPE).write(&mut unsigned_invoice.bytes).unwrap(); + BigSize(32).write(&mut unsigned_invoice.bytes).unwrap(); + [42u8; 32].write(&mut unsigned_invoice.bytes).unwrap(); + + 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(); + + if let Err(e) = StaticInvoice::try_from(encoded_invoice) { + 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(); + + BigSize(UNKNOWN_EVEN_TYPE).write(&mut unsigned_invoice.bytes).unwrap(); + BigSize(32).write(&mut unsigned_invoice.bytes).unwrap(); + [42u8; 32].write(&mut unsigned_invoice.bytes).unwrap(); + + 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 buffer = Vec::new(); + invoice.write(&mut buffer).unwrap(); + + assert!(StaticInvoice::try_from(buffer).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(); + + BigSize(UNKNOWN_ODD_TYPE).write(&mut unsigned_invoice.experimental_bytes).unwrap(); + BigSize(32).write(&mut unsigned_invoice.experimental_bytes).unwrap(); + [42u8; 32].write(&mut unsigned_invoice.experimental_bytes).unwrap(); + + 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(); + + if let Err(e) = StaticInvoice::try_from(encoded_invoice) { + 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(); + + BigSize(UNKNOWN_EVEN_TYPE).write(&mut unsigned_invoice.experimental_bytes).unwrap(); + BigSize(32).write(&mut unsigned_invoice.experimental_bytes).unwrap(); + [42u8; 32].write(&mut unsigned_invoice.experimental_bytes).unwrap(); + + 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)), + } + } + + #[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(); diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index 8ad34f2d653..78ed6b91346 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -57,7 +57,6 @@ use crate::ln::msgs::{UnsignedChannelAnnouncement, UnsignedGossipMessage}; use crate::ln::script::ShutdownScript; use crate::ln::types::PaymentPreimage; use crate::offers::invoice::UnsignedBolt12Invoice; -use crate::offers::invoice_request::UnsignedInvoiceRequest; use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer}; use crate::util::transaction_utils; @@ -870,21 +869,6 @@ pub trait NodeSigner { &self, invoice: &RawBolt11Invoice, recipient: Recipient, ) -> Result; - /// Signs the [`TaggedHash`] of a BOLT 12 invoice request. - /// - /// May be called by a function passed to [`UnsignedInvoiceRequest::sign`] where - /// `invoice_request` is the callee. - /// - /// Implementors may check that the `invoice_request` is expected rather than blindly signing - /// the tagged hash. An `Ok` result should sign `invoice_request.tagged_hash().as_digest()` with - /// the node's signing key or an ephemeral key to preserve privacy, whichever is associated with - /// [`UnsignedInvoiceRequest::payer_signing_pubkey`]. - /// - /// [`TaggedHash`]: crate::offers::merkle::TaggedHash - fn sign_bolt12_invoice_request( - &self, invoice_request: &UnsignedInvoiceRequest, - ) -> Result; - /// Signs the [`TaggedHash`] of a BOLT 12 invoice. /// /// May be called by a function passed to [`UnsignedBolt12Invoice::sign`] where `invoice` is the @@ -2185,15 +2169,6 @@ impl NodeSigner for KeysManager { Ok(self.secp_ctx.sign_ecdsa_recoverable(&hash_to_message!(&hash), secret)) } - fn sign_bolt12_invoice_request( - &self, invoice_request: &UnsignedInvoiceRequest, - ) -> Result { - let message = invoice_request.tagged_hash().as_digest(); - let keys = Keypair::from_secret_key(&self.secp_ctx, &self.node_secret); - let aux_rand = self.get_secure_random_bytes(); - Ok(self.secp_ctx.sign_schnorr_with_aux_rand(message, &keys, &aux_rand)) - } - fn sign_bolt12_invoice( &self, invoice: &UnsignedBolt12Invoice, ) -> Result { @@ -2363,12 +2338,6 @@ impl NodeSigner for PhantomKeysManager { Ok(self.inner.secp_ctx.sign_ecdsa_recoverable(&hash_to_message!(&hash), secret)) } - fn sign_bolt12_invoice_request( - &self, invoice_request: &UnsignedInvoiceRequest, - ) -> Result { - self.inner.sign_bolt12_invoice_request(invoice_request) - } - fn sign_bolt12_invoice( &self, invoice: &UnsignedBolt12Invoice, ) -> Result { diff --git a/lightning/src/util/ser.rs b/lightning/src/util/ser.rs index 99d20b927b6..2c88f913433 100644 --- a/lightning/src/util/ser.rs +++ b/lightning/src/util/ser.rs @@ -1446,6 +1446,72 @@ impl Writeable for (A, B } } +impl Readable for (A, B, C, D, E) { + fn read(r: &mut R) -> Result { + let a: A = Readable::read(r)?; + let b: B = Readable::read(r)?; + let c: C = Readable::read(r)?; + let d: D = Readable::read(r)?; + let e: E = Readable::read(r)?; + Ok((a, b, c, d, e)) + } +} +impl Writeable for (A, B, C, D, E) { + fn write(&self, w: &mut W) -> Result<(), io::Error> { + self.0.write(w)?; + self.1.write(w)?; + self.2.write(w)?; + self.3.write(w)?; + self.4.write(w) + } +} + +impl Readable for (A, B, C, D, E, F) { + fn read(r: &mut R) -> Result { + let a: A = Readable::read(r)?; + let b: B = Readable::read(r)?; + let c: C = Readable::read(r)?; + let d: D = Readable::read(r)?; + let e: E = Readable::read(r)?; + let f: F = Readable::read(r)?; + Ok((a, b, c, d, e, f)) + } +} +impl Writeable for (A, B, C, D, E, F) { + fn write(&self, w: &mut W) -> Result<(), io::Error> { + self.0.write(w)?; + self.1.write(w)?; + self.2.write(w)?; + self.3.write(w)?; + self.4.write(w)?; + self.5.write(w) + } +} + +impl Readable for (A, B, C, D, E, F, G) { + fn read(r: &mut R) -> Result { + let a: A = Readable::read(r)?; + let b: B = Readable::read(r)?; + let c: C = Readable::read(r)?; + let d: D = Readable::read(r)?; + let e: E = Readable::read(r)?; + let f: F = Readable::read(r)?; + let g: G = Readable::read(r)?; + Ok((a, b, c, d, e, f, g)) + } +} +impl Writeable for (A, B, C, D, E, F, G) { + fn write(&self, w: &mut W) -> Result<(), io::Error> { + self.0.write(w)?; + self.1.write(w)?; + self.2.write(w)?; + self.3.write(w)?; + self.4.write(w)?; + self.5.write(w)?; + self.6.write(w) + } +} + impl Writeable for () { fn write(&self, _: &mut W) -> Result<(), io::Error> { Ok(()) diff --git a/lightning/src/util/ser_macros.rs b/lightning/src/util/ser_macros.rs index d4428697b4d..0703aac9e84 100644 --- a/lightning/src/util/ser_macros.rs +++ b/lightning/src/util/ser_macros.rs @@ -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, )* } - impl<'a> $crate::util::ser::Writeable for $nameref<'a> { + impl<$($lifetime)*> $crate::util::ser::Writeable for $nameref<$($lifetime)*> { fn write(&self, writer: &mut W) -> Result<(), $crate::io::Error> { encode_tlv_stream!(writer, { $(($type, self.$field, (option, encoding: $fieldty))),* diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index 12e027d32fc..e1ec120b99a 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -34,7 +34,6 @@ use crate::ln::{msgs, wire}; use crate::ln::msgs::LightningError; use crate::ln::script::ShutdownScript; use crate::offers::invoice::UnsignedBolt12Invoice; -use crate::offers::invoice_request::UnsignedInvoiceRequest; use crate::onion_message::messenger::{DefaultMessageRouter, Destination, MessageRouter, OnionMessagePath}; use crate::routing::gossip::{EffectiveCapacity, NetworkGraph, NodeId, RoutingFees}; use crate::routing::utxo::{UtxoLookup, UtxoLookupError, UtxoResult}; @@ -1210,12 +1209,6 @@ impl NodeSigner for TestNodeSigner { unreachable!() } - fn sign_bolt12_invoice_request( - &self, _invoice_request: &UnsignedInvoiceRequest - ) -> Result { - unreachable!() - } - fn sign_bolt12_invoice( &self, _invoice: &UnsignedBolt12Invoice, ) -> Result { @@ -1263,12 +1256,6 @@ impl NodeSigner for TestKeysInterface { self.backing.sign_invoice(invoice, recipient) } - fn sign_bolt12_invoice_request( - &self, invoice_request: &UnsignedInvoiceRequest - ) -> Result { - self.backing.sign_bolt12_invoice_request(invoice_request) - } - fn sign_bolt12_invoice( &self, invoice: &UnsignedBolt12Invoice, ) -> Result {