From 0ef873e15b418ed433848d4607f825898cad6df2 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Tue, 20 Aug 2024 15:52:17 -0500 Subject: [PATCH] Disallow user-provided payer_signing_pubkey When creating an InvoiceRequests, users may choose to either use a transient signing pubkey generated by LDK or provide a static one. Disallow the latter as it allows users to reuse the same pubkey, which results in poor sender privacy. --- fuzz/src/offer_deser.rs | 43 +- lightning/src/ln/channelmanager.rs | 2 +- lightning/src/ln/outbound_payment.rs | 24 +- lightning/src/offers/invoice.rs | 327 +++++++---- lightning/src/offers/invoice_request.rs | 721 ++++++++++-------------- lightning/src/offers/merkle.rs | 56 +- lightning/src/offers/offer.rs | 120 ++-- 7 files changed, 633 insertions(+), 660 deletions(-) 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/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 6eaffa80287..bc7042c275f 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -9003,7 +9003,7 @@ where let nonce = Nonce::from_entropy_source(entropy); let builder: InvoiceRequestBuilder = offer - .request_invoice_deriving_signing_pubkey(expanded_key, nonce, secp_ctx, payment_id)? + .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 859b3467296..23ff1231a5d 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -1952,14 +1952,17 @@ mod tests { use crate::ln::types::PaymentHash; use crate::ln::channelmanager::{PaymentId, RecipientOnionFields}; use crate::ln::features::{ChannelFeatures, NodeFeatures}; + use crate::ln::inbound_payment::ExpandedKey; use crate::ln::msgs::{ErrorAction, LightningError}; use crate::ln::outbound_payment::{Bolt12PaymentError, OutboundPayments, 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::test_utils; @@ -2293,6 +2296,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(); @@ -2310,9 +2315,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(); @@ -2349,15 +2353,16 @@ mod tests { let pending_events = Mutex::new(VecDeque::new()); let outbound_payments = OutboundPayments::new(); + 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(); @@ -2410,15 +2415,16 @@ mod tests { let pending_events = Mutex::new(VecDeque::new()); let outbound_payments = OutboundPayments::new(); + 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 ff15386d6b4..ea2ffd5b592 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -1609,6 +1609,7 @@ mod tests { use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode}; 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; @@ -1651,15 +1652,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(); @@ -1667,7 +1674,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 })); @@ -1682,7 +1689,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); @@ -1709,7 +1715,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 })); @@ -1724,7 +1730,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); @@ -1747,7 +1756,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, @@ -1766,7 +1775,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, }, @@ -1905,6 +1914,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); @@ -1912,9 +1927,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() @@ -1926,9 +1940,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() @@ -1973,6 +1986,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 = BlindedPath { introduction_node: IntroductionNode::NodeId(pubkey(40)), @@ -1985,14 +1999,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) .experimental_foo(42) - .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(); if let Err(e) = invoice_request.clone() .verify_using_recipient_data(nonce, &expanded_key, &secp_ctx).unwrap() @@ -2007,14 +2021,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 issuer id instead of deriving it .experimental_foo(42) - .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(); match invoice_request .verify_using_metadata(&expanded_key, &secp_ctx).unwrap() @@ -2079,15 +2093,20 @@ 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() @@ -2101,9 +2120,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(), now - one_hour).unwrap() .relative_expiry(one_hour.as_secs() as u32 - 1) .build().unwrap() @@ -2117,13 +2135,18 @@ 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(); @@ -2134,14 +2157,19 @@ mod tests { #[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(); @@ -2153,10 +2181,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"), @@ -2166,6 +2193,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; @@ -2174,9 +2207,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(), now()).unwrap() .fallback_v0_p2wsh(&script.wscript_hash()) .fallback_v0_p2wpkh(&pubkey.wpubkey_hash().unwrap()) @@ -2213,15 +2245,20 @@ 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() @@ -2233,12 +2270,17 @@ mod tests { #[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) @@ -2250,9 +2292,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) @@ -2264,12 +2305,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(); @@ -2319,12 +2365,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(); @@ -2349,12 +2400,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() @@ -2371,12 +2427,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(); @@ -2401,12 +2462,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(); @@ -2429,12 +2495,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() @@ -2455,18 +2526,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(); @@ -2515,12 +2590,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(); @@ -2556,6 +2636,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![ BlindedPath { introduction_node: IntroductionNode::NodeId(pubkey(40)), @@ -2587,9 +2673,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() @@ -2609,9 +2694,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() @@ -2631,13 +2715,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 @@ -2651,12 +2740,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(); @@ -2676,6 +2770,11 @@ mod tests { #[test] 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); @@ -2684,9 +2783,8 @@ mod tests { let mut unsigned_invoice = OfferBuilder::new(keys.public_key()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap() + .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(); @@ -2716,9 +2814,8 @@ mod tests { let mut unsigned_invoice = OfferBuilder::new(keys.public_key()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap() + .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(); @@ -2746,14 +2843,18 @@ mod tests { #[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() - .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() .experimental_baz(42) .build().unwrap() @@ -2773,9 +2874,8 @@ mod tests { let mut unsigned_invoice = OfferBuilder::new(keys.public_key()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap() + .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(); @@ -2806,9 +2906,8 @@ mod tests { let mut unsigned_invoice = OfferBuilder::new(keys.public_key()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap() + .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(); @@ -2837,12 +2936,17 @@ mod tests { #[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(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(); @@ -2861,12 +2965,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_request.rs b/lightning/src/offers/invoice_request.rs index 610f7108c6a..9fa83e39d3c 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::{DerivedPayerSigningPubkey, 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(()) @@ -110,20 +119,6 @@ pub struct InvoiceRequestBuilder<'a, 'b, P: PayerSigningPubkeyStrategy, T: secp2 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. @@ -143,57 +138,13 @@ pub struct InvoiceRequestWithDerivedPayerSigningPubkeyBuilder<'a, 'b> { /// 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 ) => { @@ -381,6 +332,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); @@ -406,6 +363,12 @@ 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); @@ -416,11 +379,23 @@ macro_rules! invoice_request_builder_test_methods { ( 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()); + + 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, T: secp256k1::Signing> InvoiceRequestBuilder<'a, 'b, DerivedPayerSigningPubkey, T> { invoice_request_derived_payer_signing_pubkey_builder_methods!(self, Self, T); @@ -433,19 +408,6 @@ impl<'a, 'b, P: PayerSigningPubkeyStrategy, T: secp256k1::Signing> InvoiceReques 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); @@ -459,20 +421,6 @@ 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> { @@ -1337,7 +1285,7 @@ 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, TlvStream, self}; + use crate::offers::merkle::{SignatureTlvStreamRef, TaggedHash, TlvStream, self}; use crate::offers::nonce::Nonce; use crate::offers::offer::{Amount, ExperimentalOfferTlvStreamRef, OfferTlvStreamRef, Quantity}; #[cfg(not(c_bindings))] @@ -1356,51 +1304,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 })); @@ -1415,16 +1336,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, @@ -1443,7 +1367,7 @@ 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, }, @@ -1465,6 +1389,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); @@ -1472,8 +1402,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); } @@ -1482,109 +1412,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) - .experimental_foo(42) - .build().unwrap(); - let invoice_request = offer - .request_invoice_deriving_metadata(signing_pubkey, &expanded_key, nonce, payment_id) - .unwrap() - .experimental_bar(42) - .build().unwrap() - .sign(payer_sign).unwrap(); - assert_eq!(invoice_request.payer_signing_pubkey(), payer_pubkey()); - - let invoice = invoice_request.respond_with_no_std(payment_paths(), payment_hash(), now()) - .unwrap() - .experimental_baz(42) - .build().unwrap() - .sign(recipient_sign).unwrap(); - match invoice.verify_using_metadata(&expanded_key, &secp_ctx) { - 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, 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, &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 = Vec::new(); - (tlv_stream, signature_tlv_stream, experimental_tlv_stream) - .write(&mut encoded_invoice) - .unwrap(); - - let invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap(); - assert!(invoice.verify_using_metadata(&expanded_key, &secp_ctx).is_err()); - - // Fails verification with altered metadata - let ( - mut payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream, - mut signature_tlv_stream, experimental_offer_tlv_stream, - experimental_invoice_request_tlv_stream, experimental_invoice_tlv_stream, - ) = invoice.as_tlv_stream(); - let metadata = payer_tlv_stream.metadata.unwrap().iter().copied().rev().collect(); - payer_tlv_stream.metadata = Some(&metadata); - - let tlv_stream = - (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream); - let experimental_tlv_stream = ( - experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream, - experimental_invoice_tlv_stream, - ); - let mut bytes = Vec::new(); - (&tlv_stream, &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 = Vec::new(); - (tlv_stream, signature_tlv_stream, experimental_tlv_stream) - .write(&mut encoded_invoice) - .unwrap(); - - let invoice = Bolt12Invoice::try_from(encoded_invoice).unwrap(); - assert!(invoice.verify_using_metadata(&expanded_key, &secp_ctx).is_err()); - } - #[test] fn builds_invoice_request_with_derived_payer_signing_pubkey() { let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); @@ -1598,8 +1433,7 @@ mod tests { .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(); @@ -1681,16 +1515,21 @@ 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(); + .build_and_sign().unwrap(); let (_, _, tlv_stream, _, _, _) = invoice_request.as_tlv_stream(); assert_eq!(invoice_request.chain(), mainnet); assert_eq!(tlv_stream.chain, None); @@ -1699,10 +1538,9 @@ 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(); + .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)); @@ -1712,10 +1550,9 @@ 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(); + .build_and_sign().unwrap(); let (_, _, tlv_stream, _, _, _) = invoice_request.as_tlv_stream(); assert_eq!(invoice_request.chain(), mainnet); assert_eq!(tlv_stream.chain, None); @@ -1725,11 +1562,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() .chain(Network::Testnet).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap(); + .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)); @@ -1738,7 +1574,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"), @@ -1749,8 +1585,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), @@ -1759,13 +1595,18 @@ 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(); + .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)); @@ -1773,11 +1614,10 @@ 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(1001).unwrap() .amount_msats(1000).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap(); + .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)); @@ -1785,10 +1625,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(1001).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap(); + .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)); @@ -1796,7 +1635,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(999) { Ok(_) => panic!("expected error"), @@ -1807,7 +1646,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) { @@ -1818,7 +1657,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"), @@ -1829,10 +1668,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), @@ -1840,8 +1679,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), @@ -1851,9 +1690,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), @@ -1862,13 +1701,18 @@ 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(); + .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())); @@ -1876,11 +1720,10 @@ 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() .features_unchecked(InvoiceRequestFeatures::unknown()) .features_unchecked(InvoiceRequestFeatures::empty()) - .build().unwrap() - .sign(payer_sign).unwrap(); + .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); @@ -1888,6 +1731,12 @@ mod tests { #[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(); @@ -1895,9 +1744,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 (_, _, tlv_stream, _, _, _) = invoice_request.as_tlv_stream(); assert_eq!(invoice_request.quantity(), None); assert_eq!(tlv_stream.quantity, None); @@ -1906,7 +1754,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) { @@ -1918,11 +1766,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 (_, _, tlv_stream, _, _, _) = invoice_request.as_tlv_stream(); assert_eq!(invoice_request.amount_msats(), Some(10_000)); assert_eq!(tlv_stream.amount, Some(10_000)); @@ -1931,7 +1778,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) { @@ -1943,11 +1790,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 (_, _, tlv_stream, _, _, _) = invoice_request.as_tlv_stream(); assert_eq!(invoice_request.amount_msats(), Some(2_000)); assert_eq!(tlv_stream.amount, Some(2_000)); @@ -1956,8 +1802,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), @@ -1967,8 +1813,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), @@ -1977,13 +1823,18 @@ 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(); + .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"))); @@ -1991,50 +1842,29 @@ 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() .payer_note("bar".into()) .payer_note("baz".into()) - .build().unwrap() - .sign(payer_sign).unwrap(); + .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::InvalidSignature)), - } - } - #[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"), @@ -2044,12 +1874,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(); @@ -2061,13 +1896,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(); @@ -2079,10 +1919,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(); @@ -2095,12 +1934,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(); @@ -2111,10 +1955,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(); @@ -2125,9 +1968,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(); @@ -2140,10 +1982,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(); @@ -2157,9 +1998,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(); @@ -2175,10 +2015,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(); @@ -2191,6 +2030,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(); @@ -2198,9 +2043,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(); @@ -2213,11 +2057,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(); @@ -2233,11 +2076,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(); @@ -2250,11 +2092,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(); @@ -2268,11 +2109,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(); @@ -2285,9 +2125,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(); @@ -2301,9 +2140,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(); @@ -2316,11 +2154,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; @@ -2337,11 +2181,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; @@ -2356,11 +2206,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; @@ -2377,12 +2233,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(); @@ -2394,12 +2256,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); @@ -2416,16 +2283,21 @@ mod tests { #[test] 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 = 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() - .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(); @@ -2434,6 +2306,7 @@ mod tests { 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)) @@ -2450,11 +2323,11 @@ mod tests { const UNKNOWN_EVEN_TYPE: u64 = INVOICE_REQUEST_TYPES.end - 2; assert!(UNKNOWN_EVEN_TYPE % 2 == 0); - let mut unsigned_invoice_request = OfferBuilder::new(keys.public_key()) + 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() - .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(); @@ -2463,6 +2336,7 @@ mod tests { 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)) @@ -2480,16 +2354,21 @@ mod tests { #[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 mut unsigned_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() - .build().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(); @@ -2500,6 +2379,7 @@ mod tests { 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)) @@ -2516,11 +2396,11 @@ mod tests { const UNKNOWN_EVEN_TYPE: u64 = EXPERIMENTAL_INVOICE_REQUEST_TYPES.start; assert!(UNKNOWN_EVEN_TYPE % 2 == 0); - let mut unsigned_invoice_request = OfferBuilder::new(keys.public_key()) + 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() - .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(); @@ -2531,6 +2411,7 @@ mod tests { 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)) @@ -2548,18 +2429,17 @@ mod tests { #[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 keys = Keypair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap()); + let payment_id = PaymentId([1; 32]); - let invoice_request = OfferBuilder::new(keys.public_key()) + let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], keys.public_key()).unwrap() - .build().unwrap() - .sign(|message: &UnsignedInvoiceRequest| - Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) - ) - .unwrap(); + .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(); @@ -2591,6 +2471,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; @@ -2601,12 +2482,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(); @@ -2614,7 +2495,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 8c3dc770937..d013281fd17 100644 --- a/lightning/src/offers/merkle.rs +++ b/lightning/src/offers/merkle.rs @@ -293,10 +293,15 @@ mod tests { use bitcoin::hashes::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] @@ -321,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() @@ -336,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)) @@ -358,12 +370,17 @@ mod tests { #[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(vec![1; 32], payer_pubkey()).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() .payer_note("bar".into()) - .build().unwrap(); + .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. @@ -376,25 +393,21 @@ mod tests { #[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(); let tlv_stream_without_signatures = TlvStream::new(&invoice_request.bytes) @@ -412,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 5a13cb4d5a8..6d6f18e394e 100644 --- a/lightning/src/offers/offer.rs +++ b/lightning/src/offers/offer.rs @@ -99,11 +99,11 @@ use crate::util::string::PrintableString; #[cfg(not(c_bindings))] use { - crate::offers::invoice_request::{DerivedPayerSigningPubkey, ExplicitPayerSigningPubkey, InvoiceRequestBuilder}, + crate::offers::invoice_request::{DerivedPayerSigningPubkey, InvoiceRequestBuilder}, }; #[cfg(c_bindings)] use { - crate::offers::invoice_request::{InvoiceRequestWithDerivedPayerSigningPubkeyBuilder, InvoiceRequestWithExplicitPayerSigningPubkeyBuilder}, + crate::offers::invoice_request::InvoiceRequestWithDerivedPayerSigningPubkeyBuilder, }; #[allow(unused_imports)] @@ -709,23 +709,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 @@ -745,59 +745,14 @@ 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); } #[cfg(c_bindings)] impl Offer { request_invoice_derived_signing_pubkey!(self, InvoiceRequestWithDerivedPayerSigningPubkeyBuilder<'a, 'b>); - request_invoice_explicit_signing_pubkey!(self, InvoiceRequestWithExplicitPayerSigningPubkeyBuilder); } #[cfg(test)] @@ -1245,6 +1200,7 @@ mod tests { use core::time::Duration; use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode}; 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}; @@ -1370,6 +1326,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 super::OfferWithDerivedMetadataBuilder as OfferBuilder; @@ -1380,18 +1337,18 @@ mod tests { 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() ); @@ -1404,9 +1361,8 @@ mod tests { 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 @@ -1418,9 +1374,8 @@ mod tests { 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()); } @@ -1431,6 +1386,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 = BlindedPath { introduction_node: IntroductionNode::NodeId(pubkey(40)), @@ -1451,18 +1407,18 @@ mod tests { 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 @@ -1473,9 +1429,8 @@ mod tests { 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() ); @@ -1489,9 +1444,8 @@ mod tests { 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() ); @@ -1720,10 +1674,16 @@ mod tests { #[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),