From d596b4e635914ddb6d4d390e9d6190806a0a9723 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 | 23 +- lightning/src/offers/invoice.rs | 332 +++++++---- lightning/src/offers/invoice_request.rs | 729 ++++++++++-------------- lightning/src/offers/merkle.rs | 56 +- lightning/src/offers/offer.rs | 120 ++-- 7 files changed, 635 insertions(+), 670 deletions(-) diff --git a/fuzz/src/offer_deser.rs b/fuzz/src/offer_deser.rs index 84b69d3fc..f903e4885 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 4ca95db11..e639e2830 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -9612,7 +9612,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 c3fde629a..70cf67382 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -2655,6 +2655,8 @@ mod tests { let router = test_utils::TestRouter::new(network_graph, &logger, &scorer); let secp_ctx = Secp256k1::new(); let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet); + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let nonce = Nonce([0; 16]); let pending_events = Mutex::new(VecDeque::new()); let outbound_payments = OutboundPayments::new(new_hash_map()); @@ -2672,9 +2674,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(); @@ -2711,15 +2712,16 @@ mod tests { let pending_events = Mutex::new(VecDeque::new()); let outbound_payments = OutboundPayments::new(new_hash_map()); + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let nonce = Nonce([0; 16]); let payment_id = PaymentId([0; 32]); let expiration = StaleExpiration::AbsoluteTimeout(Duration::from_secs(100)); let invoice = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .build().unwrap() .sign(recipient_sign).unwrap(); @@ -2772,15 +2774,16 @@ mod tests { let pending_events = Mutex::new(VecDeque::new()); let outbound_payments = OutboundPayments::new(new_hash_map()); + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let nonce = Nonce([0; 16]); let payment_id = PaymentId([0; 32]); let expiration = StaleExpiration::AbsoluteTimeout(Duration::from_secs(100)); let invoice = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() + .build_and_sign().unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .build().unwrap() .sign(recipient_sign).unwrap(); @@ -2865,7 +2868,7 @@ mod tests { OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice_deriving_signing_pubkey(&expanded_key, nonce, &secp_ctx, payment_id) + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id) .unwrap() .build_and_sign() .unwrap() diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index b1d7660f2..59100f4cf 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -1607,6 +1607,7 @@ mod tests { use crate::blinded_path::message::BlindedMessagePath; use crate::sign::KeyMaterial; use crate::types::features::{Bolt12InvoiceFeatures, InvoiceRequestFeatures, OfferFeatures}; + use crate::ln::channelmanager::PaymentId; use crate::ln::inbound_payment::ExpandedKey; use crate::ln::msgs::DecodeError; use crate::offers::invoice_request::{ExperimentalInvoiceRequestTlvStreamRef, InvoiceRequestTlvStreamRef}; @@ -1648,15 +1649,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(); @@ -1664,7 +1671,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 })); @@ -1679,7 +1686,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); @@ -1706,7 +1712,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 })); @@ -1721,7 +1727,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); @@ -1744,7 +1753,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, @@ -1763,7 +1772,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, }, @@ -1902,6 +1911,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); @@ -1909,9 +1924,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() @@ -1923,9 +1937,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() @@ -1970,6 +1983,7 @@ mod tests { let entropy = FixedEntropy {}; let nonce = Nonce::from_entropy_source(&entropy); let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); let blinded_path = BlindedMessagePath::from_raw( pubkey(40), pubkey(41), @@ -1981,14 +1995,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() @@ -2003,14 +2017,14 @@ mod tests { invoice_request.verify_using_recipient_data(nonce, &expanded_key, &secp_ctx).is_err() ); - let offer = OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, nonce, &secp_ctx) + let invoice_request = OfferBuilder + ::deriving_signing_pubkey(node_id, &expanded_key, nonce, &secp_ctx) .amount_msats(1000) // Omit the path so that node_id is used for the signing pubkey instead of deriving it .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() @@ -2074,15 +2088,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() @@ -2096,9 +2115,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() @@ -2112,13 +2130,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(); @@ -2129,14 +2152,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(); @@ -2148,10 +2176,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"), @@ -2161,6 +2188,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; @@ -2169,9 +2202,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()) @@ -2208,15 +2240,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() @@ -2228,12 +2265,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) @@ -2245,9 +2287,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) @@ -2259,12 +2300,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(); @@ -2314,12 +2360,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(); @@ -2344,12 +2395,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() @@ -2366,12 +2422,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(); @@ -2396,12 +2457,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(); @@ -2424,12 +2490,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() @@ -2450,18 +2521,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(); @@ -2510,12 +2585,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(); @@ -2551,6 +2631,12 @@ mod tests { #[test] fn parses_invoice_with_node_id_from_blinded_path() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + let paths = vec![ BlindedMessagePath::from_raw( pubkey(40), pubkey(41), @@ -2580,9 +2666,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() @@ -2602,9 +2687,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() @@ -2624,13 +2708,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 @@ -2644,12 +2733,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(); @@ -2669,6 +2763,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); @@ -2677,9 +2776,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(); @@ -2715,9 +2813,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(); @@ -2750,14 +2847,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() @@ -2777,9 +2878,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(); @@ -2817,9 +2917,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(); @@ -2854,9 +2953,8 @@ mod tests { 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() .build().unwrap() .sign(|message: &UnsignedBolt12Invoice| @@ -2879,12 +2977,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(); @@ -2903,12 +3006,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 d1ab6d067..d66756567 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::inbound_payment::ExpandedKey; //! use lightning::types::features::OfferFeatures; //! 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> { @@ -1375,7 +1323,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))] @@ -1394,51 +1342,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 })); @@ -1453,16 +1374,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, @@ -1481,7 +1405,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, }, @@ -1503,6 +1427,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); @@ -1510,8 +1440,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); } @@ -1520,109 +1450,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])); @@ -1636,8 +1471,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(); @@ -1719,16 +1553,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); @@ -1737,10 +1576,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)); @@ -1750,10 +1588,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); @@ -1763,11 +1600,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)); @@ -1776,7 +1612,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"), @@ -1787,8 +1623,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), @@ -1797,13 +1633,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)); @@ -1811,11 +1652,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)); @@ -1823,10 +1663,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)); @@ -1834,7 +1673,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"), @@ -1845,7 +1684,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) { @@ -1856,7 +1695,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"), @@ -1867,10 +1706,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), @@ -1878,8 +1717,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), @@ -1889,9 +1728,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), @@ -1900,13 +1739,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())); @@ -1914,11 +1758,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); @@ -1926,6 +1769,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(); @@ -1933,9 +1782,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); @@ -1944,7 +1792,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) { @@ -1956,11 +1804,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)); @@ -1969,7 +1816,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) { @@ -1981,11 +1828,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)); @@ -1994,8 +1840,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), @@ -2005,8 +1851,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), @@ -2015,13 +1861,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"))); @@ -2029,50 +1880,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::IncorrectSignature)), - } - } - #[test] fn fails_responding_with_unknown_required_features() { + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); + match OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![42; 32], payer_pubkey()).unwrap() + .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() .features_unchecked(InvoiceRequestFeatures::unknown()) - .build().unwrap() - .sign(payer_sign).unwrap() + .build_and_sign().unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()) { Ok(_) => panic!("expected error"), @@ -2082,12 +1912,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(); @@ -2099,13 +1934,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(); @@ -2117,10 +1957,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(); @@ -2133,12 +1972,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(); @@ -2149,10 +1993,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(); @@ -2163,9 +2006,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(); @@ -2178,10 +2020,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(); @@ -2195,9 +2036,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(); @@ -2213,10 +2053,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(); @@ -2229,6 +2068,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(); @@ -2236,9 +2081,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(); @@ -2251,11 +2095,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(); @@ -2271,11 +2114,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(); @@ -2288,11 +2130,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(); @@ -2306,11 +2147,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(); @@ -2323,9 +2163,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(); @@ -2339,9 +2178,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(); @@ -2354,11 +2192,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; @@ -2375,11 +2219,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; @@ -2394,11 +2244,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; @@ -2415,12 +2271,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(); @@ -2432,12 +2294,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); @@ -2454,16 +2321,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(); let mut unknown_bytes = Vec::new(); BigSize(UNKNOWN_ODD_TYPE).write(&mut unknown_bytes).unwrap(); @@ -2479,6 +2351,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)) @@ -2496,11 +2369,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(); let mut unknown_bytes = Vec::new(); BigSize(UNKNOWN_EVEN_TYPE).write(&mut unknown_bytes).unwrap(); @@ -2516,6 +2389,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)) @@ -2533,16 +2407,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(); let mut unknown_bytes = Vec::new(); BigSize(UNKNOWN_ODD_TYPE).write(&mut unknown_bytes).unwrap(); @@ -2561,6 +2440,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)) @@ -2578,11 +2458,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(); let mut unknown_bytes = Vec::new(); BigSize(UNKNOWN_EVEN_TYPE).write(&mut unknown_bytes).unwrap(); @@ -2601,6 +2481,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)) @@ -2618,12 +2499,8 @@ mod tests { let invoice_request = OfferBuilder::new(keys.public_key()) .amount_msats(1000) .build().unwrap() - .request_invoice(vec![1; 32], keys.public_key()).unwrap() - .build().unwrap() - .sign(|message: &UnsignedInvoiceRequest| - Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) - ) - .unwrap(); + .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(); @@ -2640,18 +2517,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(); @@ -2683,6 +2559,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; @@ -2693,12 +2570,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(); @@ -2706,7 +2583,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 209d1ebb0..60b0353f1 100644 --- a/lightning/src/offers/merkle.rs +++ b/lightning/src/offers/merkle.rs @@ -301,10 +301,15 @@ mod tests { use bitcoin::hex::FromHex; use bitcoin::secp256k1::{Keypair, Message, Secp256k1, SecretKey}; use bitcoin::secp256k1::schnorr::Signature; + use crate::ln::channelmanager::PaymentId; + use crate::ln::inbound_payment::ExpandedKey; + use crate::offers::nonce::Nonce; use crate::offers::offer::{Amount, OfferBuilder}; use crate::offers::invoice_request::{InvoiceRequest, UnsignedInvoiceRequest}; use crate::offers::parse::Bech32Encode; - use crate::offers::test_utils::{payer_pubkey, recipient_pubkey}; + use crate::offers::signer::Metadata; + use crate::offers::test_utils::recipient_pubkey; + use crate::sign::KeyMaterial; use crate::util::ser::Writeable; #[test] @@ -329,7 +334,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() @@ -344,7 +353,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)) @@ -366,12 +378,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. @@ -384,25 +401,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) @@ -420,24 +433,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 4efd0096c..c3d8b166f 100644 --- a/lightning/src/offers/offer.rs +++ b/lightning/src/offers/offer.rs @@ -100,11 +100,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)] @@ -729,23 +729,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 @@ -765,59 +765,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)] @@ -1267,6 +1222,7 @@ mod tests { use crate::blinded_path::message::BlindedMessagePath; use crate::sign::KeyMaterial; use crate::types::features::OfferFeatures; + use crate::ln::channelmanager::PaymentId; use crate::ln::inbound_payment::ExpandedKey; use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT}; use crate::offers::nonce::Nonce; @@ -1391,6 +1347,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; @@ -1401,18 +1358,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() ); @@ -1425,9 +1382,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 @@ -1439,9 +1395,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()); } @@ -1452,6 +1407,7 @@ mod tests { let entropy = FixedEntropy {}; let nonce = Nonce::from_entropy_source(&entropy); let secp_ctx = Secp256k1::new(); + let payment_id = PaymentId([1; 32]); let blinded_path = BlindedMessagePath::from_raw( pubkey(40), pubkey(41), @@ -1471,18 +1427,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 @@ -1493,9 +1449,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() ); @@ -1509,9 +1464,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() ); @@ -1738,10 +1692,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), -- 2.39.5