Merge pull request #3264 from jkczyz/2024-08-remove-user-provided-payer-id

Disallow user-provided `payer_signing_pubkey`
This commit is contained in:
Matt Corallo 2024-11-12 20:23:45 +00:00 committed by GitHub
commit 70add1448b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 657 additions and 779 deletions

View File

@ -57,7 +57,6 @@ use lightning::ln::msgs::{
use lightning::ln::script::ShutdownScript; use lightning::ln::script::ShutdownScript;
use lightning::ln::types::ChannelId; use lightning::ln::types::ChannelId;
use lightning::offers::invoice::UnsignedBolt12Invoice; use lightning::offers::invoice::UnsignedBolt12Invoice;
use lightning::offers::invoice_request::UnsignedInvoiceRequest;
use lightning::onion_message::messenger::{Destination, MessageRouter, OnionMessagePath}; use lightning::onion_message::messenger::{Destination, MessageRouter, OnionMessagePath};
use lightning::routing::router::{InFlightHtlcs, Path, Route, RouteHop, RouteParameters, Router}; use lightning::routing::router::{InFlightHtlcs, Path, Route, RouteHop, RouteParameters, Router};
use lightning::sign::{ use lightning::sign::{
@ -340,12 +339,6 @@ impl NodeSigner for KeyProvider {
unreachable!() unreachable!()
} }
fn sign_bolt12_invoice_request(
&self, _invoice_request: &UnsignedInvoiceRequest,
) -> Result<schnorr::Signature, ()> {
unreachable!()
}
fn sign_bolt12_invoice( fn sign_bolt12_invoice(
&self, _invoice: &UnsignedBolt12Invoice, &self, _invoice: &UnsignedBolt12Invoice,
) -> Result<schnorr::Signature, ()> { ) -> Result<schnorr::Signature, ()> {

View File

@ -50,7 +50,6 @@ use lightning::ln::peer_handler::{
use lightning::ln::script::ShutdownScript; use lightning::ln::script::ShutdownScript;
use lightning::ln::types::ChannelId; use lightning::ln::types::ChannelId;
use lightning::offers::invoice::UnsignedBolt12Invoice; use lightning::offers::invoice::UnsignedBolt12Invoice;
use lightning::offers::invoice_request::UnsignedInvoiceRequest;
use lightning::onion_message::messenger::{Destination, MessageRouter, OnionMessagePath}; use lightning::onion_message::messenger::{Destination, MessageRouter, OnionMessagePath};
use lightning::routing::gossip::{NetworkGraph, P2PGossipSync}; use lightning::routing::gossip::{NetworkGraph, P2PGossipSync};
use lightning::routing::router::{ use lightning::routing::router::{
@ -413,12 +412,6 @@ impl NodeSigner for KeyProvider {
unreachable!() unreachable!()
} }
fn sign_bolt12_invoice_request(
&self, _invoice_request: &UnsignedInvoiceRequest,
) -> Result<schnorr::Signature, ()> {
unreachable!()
}
fn sign_bolt12_invoice( fn sign_bolt12_invoice(
&self, _invoice: &UnsignedBolt12Invoice, &self, _invoice: &UnsignedBolt12Invoice,
) -> Result<schnorr::Signature, ()> { ) -> Result<schnorr::Signature, ()> {

View File

@ -8,11 +8,15 @@
// licenses. // licenses.
use crate::utils::test_logger; use crate::utils::test_logger;
use bitcoin::secp256k1::{Keypair, PublicKey, Secp256k1, SecretKey}; use bitcoin::secp256k1::Secp256k1;
use core::convert::TryFrom; 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::offer::{Amount, Offer, Quantity};
use lightning::offers::parse::Bolt12SemanticError; use lightning::offers::parse::Bolt12SemanticError;
use lightning::sign::{EntropySource, KeyMaterial};
use lightning::util::ser::Writeable; use lightning::util::ser::Writeable;
#[inline] #[inline]
@ -22,27 +26,30 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], _out: Out) {
offer.write(&mut bytes).unwrap(); offer.write(&mut bytes).unwrap();
assert_eq!(data, bytes); 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(); let mut buffer = Vec::new();
if let Ok(invoice_request) = build_response(&offer, pubkey) { if let Ok(invoice_request) = build_request(&offer) {
invoice_request invoice_request.write(&mut buffer).unwrap();
.sign(|message: &UnsignedInvoiceRequest| {
Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
})
.unwrap()
.write(&mut buffer)
.unwrap();
} }
} }
} }
fn build_response( struct FixedEntropy;
offer: &Offer, pubkey: PublicKey,
) -> Result<UnsignedInvoiceRequest, Bolt12SemanticError> { impl EntropySource for FixedEntropy {
let mut builder = offer.request_invoice(vec![42; 64], pubkey)?; fn get_secure_random_bytes(&self) -> [u8; 32] {
[42; 32]
}
}
fn build_request(offer: &Offer) -> Result<InvoiceRequest, Bolt12SemanticError> {
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() { builder = match offer.amount() {
None => builder.amount_msats(1000).unwrap(), None => builder.amount_msats(1000).unwrap(),
@ -56,7 +63,7 @@ fn build_response(
Quantity::One => builder, Quantity::One => builder,
}; };
builder.build() builder.build_and_sign()
} }
pub fn offer_deser_test<Out: test_logger::Output>(data: &[u8], out: Out) { pub fn offer_deser_test<Out: test_logger::Output>(data: &[u8], out: Out) {

View File

@ -13,7 +13,6 @@ use lightning::ln::msgs::{self, DecodeError, OnionMessageHandler};
use lightning::ln::peer_handler::IgnoringMessageHandler; use lightning::ln::peer_handler::IgnoringMessageHandler;
use lightning::ln::script::ShutdownScript; use lightning::ln::script::ShutdownScript;
use lightning::offers::invoice::UnsignedBolt12Invoice; use lightning::offers::invoice::UnsignedBolt12Invoice;
use lightning::offers::invoice_request::UnsignedInvoiceRequest;
use lightning::onion_message::async_payments::{ use lightning::onion_message::async_payments::{
AsyncPaymentsMessageHandler, HeldHtlcAvailable, ReleaseHeldHtlc, AsyncPaymentsMessageHandler, HeldHtlcAvailable, ReleaseHeldHtlc,
}; };
@ -234,12 +233,6 @@ impl NodeSigner for KeyProvider {
unreachable!() unreachable!()
} }
fn sign_bolt12_invoice_request(
&self, _invoice_request: &UnsignedInvoiceRequest,
) -> Result<schnorr::Signature, ()> {
unreachable!()
}
fn sign_bolt12_invoice( fn sign_bolt12_invoice(
&self, _invoice: &UnsignedBolt12Invoice, &self, _invoice: &UnsignedBolt12Invoice,
) -> Result<schnorr::Signature, ()> { ) -> Result<schnorr::Signature, ()> {

View File

@ -27,7 +27,6 @@ use crate::ln::onion_utils;
use crate::ln::onion_utils::INVALID_ONION_BLINDING; use crate::ln::onion_utils::INVALID_ONION_BLINDING;
use crate::ln::outbound_payment::{Retry, IDEMPOTENCY_TIMEOUT_TICKS}; use crate::ln::outbound_payment::{Retry, IDEMPOTENCY_TIMEOUT_TICKS};
use crate::offers::invoice::UnsignedBolt12Invoice; use crate::offers::invoice::UnsignedBolt12Invoice;
use crate::offers::invoice_request::UnsignedInvoiceRequest;
use crate::prelude::*; use crate::prelude::*;
use crate::routing::router::{BlindedTail, Path, Payee, PaymentParameters, RouteHop, RouteParameters}; use crate::routing::router::{BlindedTail, Path, Payee, PaymentParameters, RouteHop, RouteParameters};
use crate::sign::{KeyMaterial, NodeSigner, Recipient}; use crate::sign::{KeyMaterial, NodeSigner, Recipient};
@ -1540,9 +1539,6 @@ fn route_blinding_spec_test_vector() {
fn sign_invoice( fn sign_invoice(
&self, _invoice: &RawBolt11Invoice, _recipient: Recipient, &self, _invoice: &RawBolt11Invoice, _recipient: Recipient,
) -> Result<RecoverableSignature, ()> { unreachable!() } ) -> Result<RecoverableSignature, ()> { unreachable!() }
fn sign_bolt12_invoice_request(
&self, _invoice_request: &UnsignedInvoiceRequest,
) -> Result<schnorr::Signature, ()> { unreachable!() }
fn sign_bolt12_invoice( fn sign_bolt12_invoice(
&self, _invoice: &UnsignedBolt12Invoice, &self, _invoice: &UnsignedBolt12Invoice,
) -> Result<schnorr::Signature, ()> { unreachable!() } ) -> Result<schnorr::Signature, ()> { unreachable!() }

View File

@ -66,7 +66,7 @@ use crate::ln::outbound_payment::{OutboundPayments, PendingOutboundPayment, Retr
use crate::ln::wire::Encode; use crate::ln::wire::Encode;
use crate::offers::invoice::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder, UnsignedBolt12Invoice}; use crate::offers::invoice::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder, UnsignedBolt12Invoice};
use crate::offers::invoice_error::InvoiceError; use crate::offers::invoice_error::InvoiceError;
use crate::offers::invoice_request::{DerivedPayerSigningPubkey, InvoiceRequest, InvoiceRequestBuilder}; use crate::offers::invoice_request::{InvoiceRequest, InvoiceRequestBuilder};
use crate::offers::nonce::Nonce; use crate::offers::nonce::Nonce;
use crate::offers::offer::{Offer, OfferBuilder}; use crate::offers::offer::{Offer, OfferBuilder};
use crate::offers::parse::Bolt12SemanticError; use crate::offers::parse::Bolt12SemanticError;
@ -9632,8 +9632,8 @@ where
let secp_ctx = &self.secp_ctx; let secp_ctx = &self.secp_ctx;
let nonce = Nonce::from_entropy_source(entropy); let nonce = Nonce::from_entropy_source(entropy);
let builder: InvoiceRequestBuilder<DerivedPayerSigningPubkey, secp256k1::All> = offer let builder: InvoiceRequestBuilder<secp256k1::All> = offer
.request_invoice_deriving_signing_pubkey(expanded_key, nonce, secp_ctx, payment_id)? .request_invoice(expanded_key, nonce, secp_ctx, payment_id)?
.into(); .into();
let builder = builder.chain_hash(self.chain_hash)?; let builder = builder.chain_hash(self.chain_hash)?;

View File

@ -2745,6 +2745,8 @@ mod tests {
let router = test_utils::TestRouter::new(network_graph, &logger, &scorer); let router = test_utils::TestRouter::new(network_graph, &logger, &scorer);
let secp_ctx = Secp256k1::new(); let secp_ctx = Secp256k1::new();
let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet); 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 pending_events = Mutex::new(VecDeque::new());
let outbound_payments = OutboundPayments::new(new_hash_map()); let outbound_payments = OutboundPayments::new(new_hash_map());
@ -2762,9 +2764,8 @@ mod tests {
let invoice = OfferBuilder::new(recipient_pubkey()) let invoice = OfferBuilder::new(recipient_pubkey())
.amount_msats(1000) .amount_msats(1000)
.build().unwrap() .build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.build().unwrap() .build_and_sign().unwrap()
.sign(payer_sign).unwrap()
.respond_with_no_std(payment_paths(), payment_hash(), created_at).unwrap() .respond_with_no_std(payment_paths(), payment_hash(), created_at).unwrap()
.build().unwrap() .build().unwrap()
.sign(recipient_sign).unwrap(); .sign(recipient_sign).unwrap();
@ -2801,15 +2802,16 @@ mod tests {
let pending_events = Mutex::new(VecDeque::new()); let pending_events = Mutex::new(VecDeque::new());
let outbound_payments = OutboundPayments::new(new_hash_map()); 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 payment_id = PaymentId([0; 32]);
let expiration = StaleExpiration::AbsoluteTimeout(Duration::from_secs(100)); let expiration = StaleExpiration::AbsoluteTimeout(Duration::from_secs(100));
let invoice = OfferBuilder::new(recipient_pubkey()) let invoice = OfferBuilder::new(recipient_pubkey())
.amount_msats(1000) .amount_msats(1000)
.build().unwrap() .build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.build().unwrap() .build_and_sign().unwrap()
.sign(payer_sign).unwrap()
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
.build().unwrap() .build().unwrap()
.sign(recipient_sign).unwrap(); .sign(recipient_sign).unwrap();
@ -2862,15 +2864,16 @@ mod tests {
let pending_events = Mutex::new(VecDeque::new()); let pending_events = Mutex::new(VecDeque::new());
let outbound_payments = OutboundPayments::new(new_hash_map()); 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 payment_id = PaymentId([0; 32]);
let expiration = StaleExpiration::AbsoluteTimeout(Duration::from_secs(100)); let expiration = StaleExpiration::AbsoluteTimeout(Duration::from_secs(100));
let invoice = OfferBuilder::new(recipient_pubkey()) let invoice = OfferBuilder::new(recipient_pubkey())
.amount_msats(1000) .amount_msats(1000)
.build().unwrap() .build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.build().unwrap() .build_and_sign().unwrap()
.sign(payer_sign).unwrap()
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
.build().unwrap() .build().unwrap()
.sign(recipient_sign).unwrap(); .sign(recipient_sign).unwrap();
@ -2955,7 +2958,7 @@ mod tests {
OfferBuilder::new(recipient_pubkey()) OfferBuilder::new(recipient_pubkey())
.amount_msats(1000) .amount_msats(1000)
.build().unwrap() .build().unwrap()
.request_invoice_deriving_signing_pubkey(&expanded_key, nonce, &secp_ctx, payment_id) .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id)
.unwrap() .unwrap()
.build_and_sign() .build_and_sign()
.unwrap() .unwrap()

View File

@ -1607,6 +1607,7 @@ mod tests {
use crate::blinded_path::message::BlindedMessagePath; use crate::blinded_path::message::BlindedMessagePath;
use crate::sign::KeyMaterial; use crate::sign::KeyMaterial;
use crate::types::features::{Bolt12InvoiceFeatures, InvoiceRequestFeatures, OfferFeatures}; use crate::types::features::{Bolt12InvoiceFeatures, InvoiceRequestFeatures, OfferFeatures};
use crate::ln::channelmanager::PaymentId;
use crate::ln::inbound_payment::ExpandedKey; use crate::ln::inbound_payment::ExpandedKey;
use crate::ln::msgs::DecodeError; use crate::ln::msgs::DecodeError;
use crate::offers::invoice_request::{ExperimentalInvoiceRequestTlvStreamRef, InvoiceRequestTlvStreamRef}; use crate::offers::invoice_request::{ExperimentalInvoiceRequestTlvStreamRef, InvoiceRequestTlvStreamRef};
@ -1648,15 +1649,21 @@ mod tests {
#[test] #[test]
fn builds_invoice_for_offer_with_defaults() { 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_paths = payment_paths();
let payment_hash = payment_hash(); let payment_hash = payment_hash();
let now = now(); let now = now();
let unsigned_invoice = OfferBuilder::new(recipient_pubkey()) let unsigned_invoice = OfferBuilder::new(recipient_pubkey())
.amount_msats(1000) .amount_msats(1000)
.build().unwrap() .build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.build().unwrap() .build_and_sign().unwrap()
.sign(payer_sign).unwrap()
.respond_with_no_std(payment_paths.clone(), payment_hash, now).unwrap() .respond_with_no_std(payment_paths.clone(), payment_hash, now).unwrap()
.build().unwrap(); .build().unwrap();
@ -1664,7 +1671,7 @@ mod tests {
unsigned_invoice.write(&mut buffer).unwrap(); unsigned_invoice.write(&mut buffer).unwrap();
assert_eq!(unsigned_invoice.bytes, buffer.as_slice()); 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.offer_chains(), Some(vec![ChainHash::using_genesis_block(Network::Bitcoin)]));
assert_eq!(unsigned_invoice.metadata(), None); assert_eq!(unsigned_invoice.metadata(), None);
assert_eq!(unsigned_invoice.amount(), Some(Amount::Bitcoin { amount_msats: 1000 })); 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.amount_msats(), 1000);
assert_eq!(unsigned_invoice.invoice_request_features(), &InvoiceRequestFeatures::empty()); assert_eq!(unsigned_invoice.invoice_request_features(), &InvoiceRequestFeatures::empty());
assert_eq!(unsigned_invoice.quantity(), None); 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.payer_note(), None);
assert_eq!(unsigned_invoice.payment_paths(), payment_paths.as_slice()); assert_eq!(unsigned_invoice.payment_paths(), payment_paths.as_slice());
assert_eq!(unsigned_invoice.created_at(), now); assert_eq!(unsigned_invoice.created_at(), now);
@ -1706,7 +1712,7 @@ mod tests {
invoice.write(&mut buffer).unwrap(); invoice.write(&mut buffer).unwrap();
assert_eq!(invoice.bytes, buffer.as_slice()); 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.offer_chains(), Some(vec![ChainHash::using_genesis_block(Network::Bitcoin)]));
assert_eq!(invoice.metadata(), None); assert_eq!(invoice.metadata(), None);
assert_eq!(invoice.amount(), Some(Amount::Bitcoin { amount_msats: 1000 })); 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.amount_msats(), 1000);
assert_eq!(invoice.invoice_request_features(), &InvoiceRequestFeatures::empty()); assert_eq!(invoice.invoice_request_features(), &InvoiceRequestFeatures::empty());
assert_eq!(invoice.quantity(), None); 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.payer_note(), None);
assert_eq!(invoice.payment_paths(), payment_paths.as_slice()); assert_eq!(invoice.payment_paths(), payment_paths.as_slice());
assert_eq!(invoice.created_at(), now); assert_eq!(invoice.created_at(), now);
@ -1744,7 +1753,7 @@ mod tests {
assert_eq!( assert_eq!(
invoice.as_tlv_stream(), invoice.as_tlv_stream(),
( (
PayerTlvStreamRef { metadata: Some(&vec![1; 32]) }, PayerTlvStreamRef { metadata: Some(&encrypted_payment_id.to_vec()) },
OfferTlvStreamRef { OfferTlvStreamRef {
chains: None, chains: None,
metadata: None, metadata: None,
@ -1763,7 +1772,7 @@ mod tests {
amount: None, amount: None,
features: None, features: None,
quantity: None, quantity: None,
payer_id: Some(&payer_pubkey()), payer_id: Some(&invoice.payer_signing_pubkey()),
payer_note: None, payer_note: None,
paths: None, paths: None,
offer_from_hrn: None, offer_from_hrn: None,
@ -1904,6 +1913,12 @@ mod tests {
#[cfg(feature = "std")] #[cfg(feature = "std")]
#[test] #[test]
fn builds_invoice_from_offer_with_expiration() { 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 future_expiry = Duration::from_secs(u64::max_value());
let past_expiry = Duration::from_secs(0); let past_expiry = Duration::from_secs(0);
@ -1911,9 +1926,8 @@ mod tests {
.amount_msats(1000) .amount_msats(1000)
.absolute_expiry(future_expiry) .absolute_expiry(future_expiry)
.build().unwrap() .build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.build().unwrap() .build_and_sign().unwrap()
.sign(payer_sign).unwrap()
.respond_with(payment_paths(), payment_hash()) .respond_with(payment_paths(), payment_hash())
.unwrap() .unwrap()
.build() .build()
@ -1925,9 +1939,8 @@ mod tests {
.amount_msats(1000) .amount_msats(1000)
.absolute_expiry(past_expiry) .absolute_expiry(past_expiry)
.build().unwrap() .build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.build_unchecked() .build_unchecked_and_sign()
.sign(payer_sign).unwrap()
.respond_with(payment_paths(), payment_hash()) .respond_with(payment_paths(), payment_hash())
.unwrap() .unwrap()
.build() .build()
@ -1972,6 +1985,7 @@ mod tests {
let entropy = FixedEntropy {}; let entropy = FixedEntropy {};
let nonce = Nonce::from_entropy_source(&entropy); let nonce = Nonce::from_entropy_source(&entropy);
let secp_ctx = Secp256k1::new(); let secp_ctx = Secp256k1::new();
let payment_id = PaymentId([1; 32]);
let blinded_path = BlindedMessagePath::from_raw( let blinded_path = BlindedMessagePath::from_raw(
pubkey(40), pubkey(41), pubkey(40), pubkey(41),
@ -1983,14 +1997,14 @@ mod tests {
#[cfg(c_bindings)] #[cfg(c_bindings)]
use crate::offers::offer::OfferWithDerivedMetadataBuilder as OfferBuilder; 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) .amount_msats(1000)
.path(blinded_path) .path(blinded_path)
.experimental_foo(42) .experimental_foo(42)
.build().unwrap();
let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().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() if let Err(e) = invoice_request.clone()
.verify_using_recipient_data(nonce, &expanded_key, &secp_ctx).unwrap() .verify_using_recipient_data(nonce, &expanded_key, &secp_ctx).unwrap()
@ -2005,14 +2019,14 @@ mod tests {
invoice_request.verify_using_recipient_data(nonce, &expanded_key, &secp_ctx).is_err() 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) .amount_msats(1000)
// Omit the path so that node_id is used for the signing pubkey instead of deriving it // Omit the path so that node_id is used for the signing pubkey instead of deriving it
.experimental_foo(42) .experimental_foo(42)
.build().unwrap();
let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap() .build().unwrap()
.sign(payer_sign).unwrap(); .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.build_and_sign().unwrap();
match invoice_request match invoice_request
.verify_using_metadata(&expanded_key, &secp_ctx).unwrap() .verify_using_metadata(&expanded_key, &secp_ctx).unwrap()
@ -2076,15 +2090,20 @@ mod tests {
#[test] #[test]
fn builds_invoice_with_relative_expiry() { 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 now = now();
let one_hour = Duration::from_secs(3600); let one_hour = Duration::from_secs(3600);
let invoice = OfferBuilder::new(recipient_pubkey()) let invoice = OfferBuilder::new(recipient_pubkey())
.amount_msats(1000) .amount_msats(1000)
.build().unwrap() .build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.build().unwrap() .build_and_sign().unwrap()
.sign(payer_sign).unwrap()
.respond_with_no_std(payment_paths(), payment_hash(), now).unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now).unwrap()
.relative_expiry(one_hour.as_secs() as u32) .relative_expiry(one_hour.as_secs() as u32)
.build().unwrap() .build().unwrap()
@ -2098,9 +2117,8 @@ mod tests {
let invoice = OfferBuilder::new(recipient_pubkey()) let invoice = OfferBuilder::new(recipient_pubkey())
.amount_msats(1000) .amount_msats(1000)
.build().unwrap() .build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.build().unwrap() .build_and_sign().unwrap()
.sign(payer_sign).unwrap()
.respond_with_no_std(payment_paths(), payment_hash(), now - one_hour).unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now - one_hour).unwrap()
.relative_expiry(one_hour.as_secs() as u32 - 1) .relative_expiry(one_hour.as_secs() as u32 - 1)
.build().unwrap() .build().unwrap()
@ -2114,13 +2132,18 @@ mod tests {
#[test] #[test]
fn builds_invoice_with_amount_from_request() { 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()) let invoice = OfferBuilder::new(recipient_pubkey())
.amount_msats(1000) .amount_msats(1000)
.build().unwrap() .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(1001).unwrap()
.build().unwrap() .build_and_sign().unwrap()
.sign(payer_sign).unwrap()
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
.build().unwrap() .build().unwrap()
.sign(recipient_sign).unwrap(); .sign(recipient_sign).unwrap();
@ -2131,14 +2154,19 @@ mod tests {
#[test] #[test]
fn builds_invoice_with_quantity_from_request() { 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()) let invoice = OfferBuilder::new(recipient_pubkey())
.amount_msats(1000) .amount_msats(1000)
.supported_quantity(Quantity::Unbounded) .supported_quantity(Quantity::Unbounded)
.build().unwrap() .build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.quantity(2).unwrap() .quantity(2).unwrap()
.build().unwrap() .build_and_sign().unwrap()
.sign(payer_sign).unwrap()
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
.build().unwrap() .build().unwrap()
.sign(recipient_sign).unwrap(); .sign(recipient_sign).unwrap();
@ -2150,10 +2178,9 @@ mod tests {
.amount_msats(1000) .amount_msats(1000)
.supported_quantity(Quantity::Unbounded) .supported_quantity(Quantity::Unbounded)
.build().unwrap() .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() .quantity(u64::max_value()).unwrap()
.build_unchecked() .build_unchecked_and_sign()
.sign(payer_sign).unwrap()
.respond_with_no_std(payment_paths(), payment_hash(), now()) .respond_with_no_std(payment_paths(), payment_hash(), now())
{ {
Ok(_) => panic!("expected error"), Ok(_) => panic!("expected error"),
@ -2163,6 +2190,12 @@ mod tests {
#[test] #[test]
fn builds_invoice_with_fallback_address() { 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 script = ScriptBuf::new();
let pubkey = bitcoin::key::PublicKey::new(recipient_pubkey()); let pubkey = bitcoin::key::PublicKey::new(recipient_pubkey());
let x_only_pubkey = XOnlyPublicKey::from_keypair(&recipient_keys()).0; let x_only_pubkey = XOnlyPublicKey::from_keypair(&recipient_keys()).0;
@ -2171,9 +2204,8 @@ mod tests {
let invoice = OfferBuilder::new(recipient_pubkey()) let invoice = OfferBuilder::new(recipient_pubkey())
.amount_msats(1000) .amount_msats(1000)
.build().unwrap() .build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.build().unwrap() .build_and_sign().unwrap()
.sign(payer_sign).unwrap()
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
.fallback_v0_p2wsh(&script.wscript_hash()) .fallback_v0_p2wsh(&script.wscript_hash())
.fallback_v0_p2wpkh(&pubkey.wpubkey_hash().unwrap()) .fallback_v0_p2wpkh(&pubkey.wpubkey_hash().unwrap())
@ -2210,15 +2242,20 @@ mod tests {
#[test] #[test]
fn builds_invoice_with_allow_mpp() { 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(); let mut features = Bolt12InvoiceFeatures::empty();
features.set_basic_mpp_optional(); features.set_basic_mpp_optional();
let invoice = OfferBuilder::new(recipient_pubkey()) let invoice = OfferBuilder::new(recipient_pubkey())
.amount_msats(1000) .amount_msats(1000)
.build().unwrap() .build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.build().unwrap() .build_and_sign().unwrap()
.sign(payer_sign).unwrap()
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
.allow_mpp() .allow_mpp()
.build().unwrap() .build().unwrap()
@ -2230,12 +2267,17 @@ mod tests {
#[test] #[test]
fn fails_signing_invoice() { 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()) match OfferBuilder::new(recipient_pubkey())
.amount_msats(1000) .amount_msats(1000)
.build().unwrap() .build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.build().unwrap() .build_and_sign().unwrap()
.sign(payer_sign).unwrap()
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
.build().unwrap() .build().unwrap()
.sign(fail_sign) .sign(fail_sign)
@ -2247,9 +2289,8 @@ mod tests {
match OfferBuilder::new(recipient_pubkey()) match OfferBuilder::new(recipient_pubkey())
.amount_msats(1000) .amount_msats(1000)
.build().unwrap() .build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.build().unwrap() .build_and_sign().unwrap()
.sign(payer_sign).unwrap()
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
.build().unwrap() .build().unwrap()
.sign(payer_sign) .sign(payer_sign)
@ -2261,12 +2302,17 @@ mod tests {
#[test] #[test]
fn parses_invoice_with_payment_paths() { 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()) let invoice = OfferBuilder::new(recipient_pubkey())
.amount_msats(1000) .amount_msats(1000)
.build().unwrap() .build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.build().unwrap() .build_and_sign().unwrap()
.sign(payer_sign).unwrap()
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
.build().unwrap() .build().unwrap()
.sign(recipient_sign).unwrap(); .sign(recipient_sign).unwrap();
@ -2316,12 +2362,17 @@ mod tests {
#[test] #[test]
fn parses_invoice_with_created_at() { 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()) let invoice = OfferBuilder::new(recipient_pubkey())
.amount_msats(1000) .amount_msats(1000)
.build().unwrap() .build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.build().unwrap() .build_and_sign().unwrap()
.sign(payer_sign).unwrap()
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
.build().unwrap() .build().unwrap()
.sign(recipient_sign).unwrap(); .sign(recipient_sign).unwrap();
@ -2346,12 +2397,17 @@ mod tests {
#[test] #[test]
fn parses_invoice_with_relative_expiry() { 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()) let invoice = OfferBuilder::new(recipient_pubkey())
.amount_msats(1000) .amount_msats(1000)
.build().unwrap() .build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.build().unwrap() .build_and_sign().unwrap()
.sign(payer_sign).unwrap()
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
.relative_expiry(3600) .relative_expiry(3600)
.build().unwrap() .build().unwrap()
@ -2368,12 +2424,17 @@ mod tests {
#[test] #[test]
fn parses_invoice_with_payment_hash() { 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()) let invoice = OfferBuilder::new(recipient_pubkey())
.amount_msats(1000) .amount_msats(1000)
.build().unwrap() .build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.build().unwrap() .build_and_sign().unwrap()
.sign(payer_sign).unwrap()
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
.build().unwrap() .build().unwrap()
.sign(recipient_sign).unwrap(); .sign(recipient_sign).unwrap();
@ -2398,12 +2459,17 @@ mod tests {
#[test] #[test]
fn parses_invoice_with_amount() { 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()) let invoice = OfferBuilder::new(recipient_pubkey())
.amount_msats(1000) .amount_msats(1000)
.build().unwrap() .build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.build().unwrap() .build_and_sign().unwrap()
.sign(payer_sign).unwrap()
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
.build().unwrap() .build().unwrap()
.sign(recipient_sign).unwrap(); .sign(recipient_sign).unwrap();
@ -2426,12 +2492,17 @@ mod tests {
#[test] #[test]
fn parses_invoice_with_allow_mpp() { 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()) let invoice = OfferBuilder::new(recipient_pubkey())
.amount_msats(1000) .amount_msats(1000)
.build().unwrap() .build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.build().unwrap() .build_and_sign().unwrap()
.sign(payer_sign).unwrap()
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
.allow_mpp() .allow_mpp()
.build().unwrap() .build().unwrap()
@ -2452,18 +2523,22 @@ mod tests {
#[test] #[test]
fn parses_invoice_with_fallback_address() { 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 script = ScriptBuf::new();
let pubkey = bitcoin::key::PublicKey::new(recipient_pubkey()); let pubkey = bitcoin::key::PublicKey::new(recipient_pubkey());
let x_only_pubkey = XOnlyPublicKey::from_keypair(&recipient_keys()).0; let x_only_pubkey = XOnlyPublicKey::from_keypair(&recipient_keys()).0;
let tweaked_pubkey = TweakedPublicKey::dangerous_assume_tweaked(x_only_pubkey); 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) .amount_msats(1000)
.build().unwrap();
let invoice_request = offer
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().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))] #[cfg(not(c_bindings))]
let invoice_builder = invoice_request let invoice_builder = invoice_request
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap(); .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap();
@ -2512,12 +2587,17 @@ mod tests {
#[test] #[test]
fn parses_invoice_with_node_id() { 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()) let invoice = OfferBuilder::new(recipient_pubkey())
.amount_msats(1000) .amount_msats(1000)
.build().unwrap() .build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.build().unwrap() .build_and_sign().unwrap()
.sign(payer_sign).unwrap()
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
.build().unwrap() .build().unwrap()
.sign(recipient_sign).unwrap(); .sign(recipient_sign).unwrap();
@ -2553,6 +2633,12 @@ mod tests {
#[test] #[test]
fn parses_invoice_with_node_id_from_blinded_path() { 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![ let paths = vec![
BlindedMessagePath::from_raw( BlindedMessagePath::from_raw(
pubkey(40), pubkey(41), pubkey(40), pubkey(41),
@ -2582,9 +2668,8 @@ mod tests {
.path(paths[0].clone()) .path(paths[0].clone())
.path(paths[1].clone()) .path(paths[1].clone())
.build().unwrap() .build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.build().unwrap() .build_and_sign().unwrap()
.sign(payer_sign).unwrap()
.respond_with_no_std_using_signing_pubkey( .respond_with_no_std_using_signing_pubkey(
payment_paths(), payment_hash(), now(), pubkey(46) payment_paths(), payment_hash(), now(), pubkey(46)
).unwrap() ).unwrap()
@ -2604,9 +2689,8 @@ mod tests {
.path(paths[0].clone()) .path(paths[0].clone())
.path(paths[1].clone()) .path(paths[1].clone())
.build().unwrap() .build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.build().unwrap() .build_and_sign().unwrap()
.sign(payer_sign).unwrap()
.respond_with_no_std_using_signing_pubkey( .respond_with_no_std_using_signing_pubkey(
payment_paths(), payment_hash(), now(), recipient_pubkey() payment_paths(), payment_hash(), now(), recipient_pubkey()
).unwrap() ).unwrap()
@ -2626,13 +2710,18 @@ mod tests {
#[test] #[test]
fn fails_parsing_invoice_without_signature() { 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(); let mut buffer = Vec::new();
OfferBuilder::new(recipient_pubkey()) OfferBuilder::new(recipient_pubkey())
.amount_msats(1000) .amount_msats(1000)
.build().unwrap() .build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.build().unwrap() .build_and_sign().unwrap()
.sign(payer_sign).unwrap()
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
.build().unwrap() .build().unwrap()
.contents .contents
@ -2646,12 +2735,17 @@ mod tests {
#[test] #[test]
fn fails_parsing_invoice_with_invalid_signature() { 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()) let mut invoice = OfferBuilder::new(recipient_pubkey())
.amount_msats(1000) .amount_msats(1000)
.build().unwrap() .build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.build().unwrap() .build_and_sign().unwrap()
.sign(payer_sign).unwrap()
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
.build().unwrap() .build().unwrap()
.sign(recipient_sign).unwrap(); .sign(recipient_sign).unwrap();
@ -2671,6 +2765,11 @@ mod tests {
#[test] #[test]
fn parses_invoice_with_unknown_tlv_records() { 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; const UNKNOWN_ODD_TYPE: u64 = INVOICE_TYPES.end - 1;
assert!(UNKNOWN_ODD_TYPE % 2 == 1); assert!(UNKNOWN_ODD_TYPE % 2 == 1);
@ -2679,9 +2778,8 @@ mod tests {
let mut unsigned_invoice = OfferBuilder::new(keys.public_key()) let mut unsigned_invoice = OfferBuilder::new(keys.public_key())
.amount_msats(1000) .amount_msats(1000)
.build().unwrap() .build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.build().unwrap() .build_and_sign().unwrap()
.sign(payer_sign).unwrap()
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
.build().unwrap(); .build().unwrap();
@ -2717,9 +2815,8 @@ mod tests {
let mut unsigned_invoice = OfferBuilder::new(keys.public_key()) let mut unsigned_invoice = OfferBuilder::new(keys.public_key())
.amount_msats(1000) .amount_msats(1000)
.build().unwrap() .build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.build().unwrap() .build_and_sign().unwrap()
.sign(payer_sign).unwrap()
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
.build().unwrap(); .build().unwrap();
@ -2752,14 +2849,18 @@ mod tests {
#[test] #[test]
fn parses_invoice_with_experimental_tlv_records() { 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 secp_ctx = Secp256k1::new();
let keys = Keypair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap()); let keys = Keypair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
let invoice = OfferBuilder::new(keys.public_key()) let invoice = OfferBuilder::new(keys.public_key())
.amount_msats(1000) .amount_msats(1000)
.build().unwrap() .build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.build().unwrap() .build_and_sign().unwrap()
.sign(payer_sign).unwrap()
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
.experimental_baz(42) .experimental_baz(42)
.build().unwrap() .build().unwrap()
@ -2779,9 +2880,8 @@ mod tests {
let mut unsigned_invoice = OfferBuilder::new(keys.public_key()) let mut unsigned_invoice = OfferBuilder::new(keys.public_key())
.amount_msats(1000) .amount_msats(1000)
.build().unwrap() .build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.build().unwrap() .build_and_sign().unwrap()
.sign(payer_sign).unwrap()
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
.build().unwrap(); .build().unwrap();
@ -2819,9 +2919,8 @@ mod tests {
let mut unsigned_invoice = OfferBuilder::new(keys.public_key()) let mut unsigned_invoice = OfferBuilder::new(keys.public_key())
.amount_msats(1000) .amount_msats(1000)
.build().unwrap() .build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.build().unwrap() .build_and_sign().unwrap()
.sign(payer_sign).unwrap()
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
.build().unwrap(); .build().unwrap();
@ -2856,9 +2955,8 @@ mod tests {
let invoice = OfferBuilder::new(keys.public_key()) let invoice = OfferBuilder::new(keys.public_key())
.amount_msats(1000) .amount_msats(1000)
.build().unwrap() .build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.build().unwrap() .build_and_sign().unwrap()
.sign(payer_sign).unwrap()
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
.build().unwrap() .build().unwrap()
.sign(|message: &UnsignedBolt12Invoice| .sign(|message: &UnsignedBolt12Invoice|
@ -2881,12 +2979,17 @@ mod tests {
#[test] #[test]
fn fails_parsing_invoice_with_out_of_range_tlv_records() { 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()) let invoice = OfferBuilder::new(recipient_pubkey())
.amount_msats(1000) .amount_msats(1000)
.build().unwrap() .build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.build().unwrap() .build_and_sign().unwrap()
.sign(payer_sign).unwrap()
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
.build().unwrap() .build().unwrap()
.sign(recipient_sign).unwrap(); .sign(recipient_sign).unwrap();
@ -2905,12 +3008,17 @@ mod tests {
#[test] #[test]
fn fails_parsing_invoice_with_message_paths() { 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()) let invoice = OfferBuilder::new(recipient_pubkey())
.amount_msats(1000) .amount_msats(1000)
.build().unwrap() .build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.build().unwrap() .build_and_sign().unwrap()
.sign(payer_sign).unwrap()
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
.build().unwrap() .build().unwrap()
.sign(recipient_sign).unwrap(); .sign(recipient_sign).unwrap();

File diff suppressed because it is too large Load Diff

View File

@ -301,10 +301,15 @@ mod tests {
use bitcoin::hex::FromHex; use bitcoin::hex::FromHex;
use bitcoin::secp256k1::{Keypair, Message, Secp256k1, SecretKey}; use bitcoin::secp256k1::{Keypair, Message, Secp256k1, SecretKey};
use bitcoin::secp256k1::schnorr::Signature; 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::offer::{Amount, OfferBuilder};
use crate::offers::invoice_request::{InvoiceRequest, UnsignedInvoiceRequest}; use crate::offers::invoice_request::{InvoiceRequest, UnsignedInvoiceRequest};
use crate::offers::parse::Bech32Encode; 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; use crate::util::ser::Writeable;
#[test] #[test]
@ -329,7 +334,11 @@ mod tests {
#[test] #[test]
fn calculates_merkle_root_hash_from_invoice_request() { 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 secp_ctx = Secp256k1::new();
let payment_id = PaymentId([1; 32]);
let recipient_pubkey = { let recipient_pubkey = {
let secret_key = SecretKey::from_slice(&<Vec<u8>>::from_hex("4141414141414141414141414141414141414141414141414141414141414141").unwrap()).unwrap(); let secret_key = SecretKey::from_slice(&<Vec<u8>>::from_hex("4141414141414141414141414141414141414141414141414141414141414141").unwrap()).unwrap();
Keypair::from_secret_key(&secp_ctx, &secret_key).public_key() Keypair::from_secret_key(&secp_ctx, &secret_key).public_key()
@ -344,7 +353,10 @@ mod tests {
.description("A Mathematical Treatise".into()) .description("A Mathematical Treatise".into())
.amount(Amount::Currency { iso4217_code: *b"USD", amount: 100 }) .amount(Amount::Currency { iso4217_code: *b"USD", amount: 100 })
.build_unchecked() .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() .build_unchecked()
.sign(|message: &UnsignedInvoiceRequest| .sign(|message: &UnsignedInvoiceRequest|
Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &payer_keys)) Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &payer_keys))
@ -364,45 +376,46 @@ mod tests {
); );
} }
#[test] #[test]
fn compute_tagged_hash() { fn compute_tagged_hash() {
let unsigned_invoice_request = OfferBuilder::new(recipient_pubkey()) let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
.amount_msats(1000) let nonce = Nonce([0u8; 16]);
.build().unwrap() let secp_ctx = Secp256k1::new();
.request_invoice(vec![1; 32], payer_pubkey()).unwrap() let payment_id = PaymentId([1; 32]);
.payer_note("bar".into())
.build().unwrap();
// Simply test that we can grab the tag and merkle root exposed by the accessor let unsigned_invoice_request = OfferBuilder::new(recipient_pubkey())
// functions, then use them to succesfully compute a tagged hash. .amount_msats(1000)
let tagged_hash = unsigned_invoice_request.as_ref(); .build().unwrap()
let expected_digest = unsigned_invoice_request.as_ref().as_digest(); .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
let tag = sha256::Hash::hash(tagged_hash.tag().as_bytes()); .payer_note("bar".into())
let actual_digest = Message::from_digest(super::tagged_hash(tag, tagged_hash.merkle_root()).to_byte_array()); .build_unchecked();
assert_eq!(*expected_digest, actual_digest);
} // Simply test that we can grab the tag and merkle root exposed by the accessor
// functions, then use them to succesfully compute a tagged hash.
let tagged_hash = unsigned_invoice_request.as_ref();
let expected_digest = unsigned_invoice_request.as_ref().as_digest();
let tag = sha256::Hash::hash(tagged_hash.tag().as_bytes());
let actual_digest = Message::from_digest(super::tagged_hash(tag, tagged_hash.merkle_root()).to_byte_array());
assert_eq!(*expected_digest, actual_digest);
}
#[test] #[test]
fn skips_encoding_signature_tlv_records() { 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 secp_ctx = Secp256k1::new();
let payment_id = PaymentId([1; 32]);
let recipient_pubkey = { let recipient_pubkey = {
let secret_key = SecretKey::from_slice(&[41; 32]).unwrap(); let secret_key = SecretKey::from_slice(&[41; 32]).unwrap();
Keypair::from_secret_key(&secp_ctx, &secret_key).public_key() 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) let invoice_request = OfferBuilder::new(recipient_pubkey)
.amount_msats(100) .amount_msats(100)
.build_unchecked() .build_unchecked()
.request_invoice(vec![0; 8], payer_keys.public_key()).unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.build_unchecked() .build_and_sign().unwrap();
.sign(|message: &UnsignedInvoiceRequest|
Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &payer_keys))
)
.unwrap();
let mut bytes_without_signature = Vec::new(); let mut bytes_without_signature = Vec::new();
let tlv_stream_without_signatures = TlvStream::new(&invoice_request.bytes) let tlv_stream_without_signatures = TlvStream::new(&invoice_request.bytes)
@ -420,24 +433,21 @@ mod tests {
#[test] #[test]
fn iterates_over_tlv_stream_range() { 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 secp_ctx = Secp256k1::new();
let payment_id = PaymentId([1; 32]);
let recipient_pubkey = { let recipient_pubkey = {
let secret_key = SecretKey::from_slice(&[41; 32]).unwrap(); let secret_key = SecretKey::from_slice(&[41; 32]).unwrap();
Keypair::from_secret_key(&secp_ctx, &secret_key).public_key() 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) let invoice_request = OfferBuilder::new(recipient_pubkey)
.amount_msats(100) .amount_msats(100)
.build_unchecked() .build_unchecked()
.request_invoice(vec![0; 8], payer_keys.public_key()).unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.build_unchecked() .build_and_sign()
.sign(|message: &UnsignedInvoiceRequest|
Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &payer_keys))
)
.unwrap(); .unwrap();
let tlv_stream = TlvStream::new(&invoice_request.bytes).range(0..1) let tlv_stream = TlvStream::new(&invoice_request.bytes).range(0..1)

View File

@ -100,11 +100,11 @@ use crate::util::string::PrintableString;
#[cfg(not(c_bindings))] #[cfg(not(c_bindings))]
use { use {
crate::offers::invoice_request::{DerivedPayerSigningPubkey, ExplicitPayerSigningPubkey, InvoiceRequestBuilder}, crate::offers::invoice_request::InvoiceRequestBuilder,
}; };
#[cfg(c_bindings)] #[cfg(c_bindings)]
use { use {
crate::offers::invoice_request::{InvoiceRequestWithDerivedPayerSigningPubkeyBuilder, InvoiceRequestWithExplicitPayerSigningPubkeyBuilder}, crate::offers::invoice_request::InvoiceRequestWithDerivedPayerSigningPubkeyBuilder,
}; };
#[allow(unused_imports)] #[allow(unused_imports)]
@ -729,23 +729,23 @@ impl Offer {
} }
macro_rules! request_invoice_derived_signing_pubkey { ($self: ident, $builder: ty) => { 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 /// - derives the [`InvoiceRequest::payer_signing_pubkey`] such that a different key can be used
/// for each request, /// for each request in order to protect the sender's privacy,
/// - sets [`InvoiceRequest::payer_metadata`] when [`InvoiceRequestBuilder::build`] is called /// - sets [`InvoiceRequest::payer_metadata`] when [`InvoiceRequestBuilder::build_and_sign`] is
/// such that it can be used by [`Bolt12Invoice::verify_using_metadata`] to determine if the /// called such that it can be used by [`Bolt12Invoice::verify_using_metadata`] to determine
/// invoice was requested using a base [`ExpandedKey`] from which the payer id was derived, /// if the invoice was requested using a base [`ExpandedKey`] from which the payer id was
/// and /// derived, and
/// - includes the [`PaymentId`] encrypted in [`InvoiceRequest::payer_metadata`] so that it can /// - includes the [`PaymentId`] encrypted in [`InvoiceRequest::payer_metadata`] so that it can
/// be used when sending the payment for the requested invoice. /// 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_signing_pubkey`]: crate::offers::invoice_request::InvoiceRequest::payer_signing_pubkey
/// [`InvoiceRequest::payer_metadata`]: crate::offers::invoice_request::InvoiceRequest::payer_metadata /// [`InvoiceRequest::payer_metadata`]: crate::offers::invoice_request::InvoiceRequest::payer_metadata
/// [`Bolt12Invoice::verify_using_metadata`]: crate::offers::invoice::Bolt12Invoice::verify_using_metadata /// [`Bolt12Invoice::verify_using_metadata`]: crate::offers::invoice::Bolt12Invoice::verify_using_metadata
/// [`ExpandedKey`]: crate::ln::inbound_payment::ExpandedKey /// [`ExpandedKey`]: crate::ln::inbound_payment::ExpandedKey
pub fn request_invoice_deriving_signing_pubkey< pub fn request_invoice<
'a, 'b, 'a, 'b,
#[cfg(not(c_bindings))] #[cfg(not(c_bindings))]
T: secp256k1::Signing 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<u8>, 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))] #[cfg(not(c_bindings))]
impl Offer { impl Offer {
request_invoice_derived_signing_pubkey!(self, InvoiceRequestBuilder<'a, 'b, DerivedPayerSigningPubkey, T>); request_invoice_derived_signing_pubkey!(self, InvoiceRequestBuilder<'a, 'b, T>);
request_invoice_explicit_signing_pubkey!(self, InvoiceRequestBuilder<ExplicitPayerSigningPubkey, secp256k1::SignOnly>);
} }
#[cfg(c_bindings)] #[cfg(c_bindings)]
impl Offer { impl Offer {
request_invoice_derived_signing_pubkey!(self, InvoiceRequestWithDerivedPayerSigningPubkeyBuilder<'a, 'b>); request_invoice_derived_signing_pubkey!(self, InvoiceRequestWithDerivedPayerSigningPubkeyBuilder<'a, 'b>);
request_invoice_explicit_signing_pubkey!(self, InvoiceRequestWithExplicitPayerSigningPubkeyBuilder);
} }
#[cfg(test)] #[cfg(test)]
@ -1267,6 +1222,7 @@ mod tests {
use crate::blinded_path::message::BlindedMessagePath; use crate::blinded_path::message::BlindedMessagePath;
use crate::sign::KeyMaterial; use crate::sign::KeyMaterial;
use crate::types::features::OfferFeatures; use crate::types::features::OfferFeatures;
use crate::ln::channelmanager::PaymentId;
use crate::ln::inbound_payment::ExpandedKey; use crate::ln::inbound_payment::ExpandedKey;
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT}; use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
use crate::offers::nonce::Nonce; use crate::offers::nonce::Nonce;
@ -1391,6 +1347,7 @@ mod tests {
let entropy = FixedEntropy {}; let entropy = FixedEntropy {};
let nonce = Nonce::from_entropy_source(&entropy); let nonce = Nonce::from_entropy_source(&entropy);
let secp_ctx = Secp256k1::new(); let secp_ctx = Secp256k1::new();
let payment_id = PaymentId([1; 32]);
#[cfg(c_bindings)] #[cfg(c_bindings)]
use super::OfferWithDerivedMetadataBuilder as OfferBuilder; use super::OfferWithDerivedMetadataBuilder as OfferBuilder;
@ -1401,18 +1358,18 @@ mod tests {
assert!(offer.metadata().is_some()); assert!(offer.metadata().is_some());
assert_eq!(offer.issuer_signing_pubkey(), Some(node_id)); 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
.build().unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.sign(payer_sign).unwrap(); .build_and_sign().unwrap();
match invoice_request.verify_using_metadata(&expanded_key, &secp_ctx) { match invoice_request.verify_using_metadata(&expanded_key, &secp_ctx) {
Ok(invoice_request) => assert_eq!(invoice_request.offer_id, offer.id()), Ok(invoice_request) => assert_eq!(invoice_request.offer_id, offer.id()),
Err(_) => panic!("unexpected error"), Err(_) => panic!("unexpected error"),
} }
// Fails verification when using the wrong method // Fails verification when using the wrong method
let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap() let invoice_request = offer
.build().unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.sign(payer_sign).unwrap(); .build_and_sign().unwrap();
assert!( assert!(
invoice_request.verify_using_recipient_data(nonce, &expanded_key, &secp_ctx).is_err() 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(); tlv_stream.write(&mut encoded_offer).unwrap();
let invoice_request = Offer::try_from(encoded_offer).unwrap() let invoice_request = Offer::try_from(encoded_offer).unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.build().unwrap() .build_and_sign().unwrap();
.sign(payer_sign).unwrap();
assert!(invoice_request.verify_using_metadata(&expanded_key, &secp_ctx).is_err()); assert!(invoice_request.verify_using_metadata(&expanded_key, &secp_ctx).is_err());
// Fails verification with altered metadata // Fails verification with altered metadata
@ -1439,9 +1395,8 @@ mod tests {
tlv_stream.write(&mut encoded_offer).unwrap(); tlv_stream.write(&mut encoded_offer).unwrap();
let invoice_request = Offer::try_from(encoded_offer).unwrap() let invoice_request = Offer::try_from(encoded_offer).unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.build().unwrap() .build_and_sign().unwrap();
.sign(payer_sign).unwrap();
assert!(invoice_request.verify_using_metadata(&expanded_key, &secp_ctx).is_err()); assert!(invoice_request.verify_using_metadata(&expanded_key, &secp_ctx).is_err());
} }
@ -1452,6 +1407,7 @@ mod tests {
let entropy = FixedEntropy {}; let entropy = FixedEntropy {};
let nonce = Nonce::from_entropy_source(&entropy); let nonce = Nonce::from_entropy_source(&entropy);
let secp_ctx = Secp256k1::new(); let secp_ctx = Secp256k1::new();
let payment_id = PaymentId([1; 32]);
let blinded_path = BlindedMessagePath::from_raw( let blinded_path = BlindedMessagePath::from_raw(
pubkey(40), pubkey(41), pubkey(40), pubkey(41),
@ -1471,18 +1427,18 @@ mod tests {
assert!(offer.metadata().is_none()); assert!(offer.metadata().is_none());
assert_ne!(offer.issuer_signing_pubkey(), Some(node_id)); assert_ne!(offer.issuer_signing_pubkey(), Some(node_id));
let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap() let invoice_request = offer
.build().unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.sign(payer_sign).unwrap(); .build_and_sign().unwrap();
match invoice_request.verify_using_recipient_data(nonce, &expanded_key, &secp_ctx) { match invoice_request.verify_using_recipient_data(nonce, &expanded_key, &secp_ctx) {
Ok(invoice_request) => assert_eq!(invoice_request.offer_id, offer.id()), Ok(invoice_request) => assert_eq!(invoice_request.offer_id, offer.id()),
Err(_) => panic!("unexpected error"), Err(_) => panic!("unexpected error"),
} }
// Fails verification when using the wrong method // Fails verification when using the wrong method
let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap() let invoice_request = offer
.build().unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.sign(payer_sign).unwrap(); .build_and_sign().unwrap();
assert!(invoice_request.verify_using_metadata(&expanded_key, &secp_ctx).is_err()); assert!(invoice_request.verify_using_metadata(&expanded_key, &secp_ctx).is_err());
// Fails verification with altered offer field // Fails verification with altered offer field
@ -1493,9 +1449,8 @@ mod tests {
tlv_stream.write(&mut encoded_offer).unwrap(); tlv_stream.write(&mut encoded_offer).unwrap();
let invoice_request = Offer::try_from(encoded_offer).unwrap() let invoice_request = Offer::try_from(encoded_offer).unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.build().unwrap() .build_and_sign().unwrap();
.sign(payer_sign).unwrap();
assert!( assert!(
invoice_request.verify_using_recipient_data(nonce, &expanded_key, &secp_ctx).is_err() 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(); tlv_stream.write(&mut encoded_offer).unwrap();
let invoice_request = Offer::try_from(encoded_offer).unwrap() let invoice_request = Offer::try_from(encoded_offer).unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.build().unwrap() .build_and_sign().unwrap();
.sign(payer_sign).unwrap();
assert!( assert!(
invoice_request.verify_using_recipient_data(nonce, &expanded_key, &secp_ctx).is_err() invoice_request.verify_using_recipient_data(nonce, &expanded_key, &secp_ctx).is_err()
); );
@ -1738,10 +1692,16 @@ mod tests {
#[test] #[test]
fn fails_requesting_invoice_with_unknown_required_features() { 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)) match OfferBuilder::new(pubkey(42))
.features_unchecked(OfferFeatures::unknown()) .features_unchecked(OfferFeatures::unknown())
.build().unwrap() .build().unwrap()
.request_invoice(vec![1; 32], pubkey(43)) .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id)
{ {
Ok(_) => panic!("expected error"), Ok(_) => panic!("expected error"),
Err(e) => assert_eq!(e, Bolt12SemanticError::UnknownRequiredFeatures), Err(e) => assert_eq!(e, Bolt12SemanticError::UnknownRequiredFeatures),

View File

@ -56,7 +56,6 @@ use crate::ln::msgs::PartialSignatureWithNonce;
use crate::ln::msgs::{UnsignedChannelAnnouncement, UnsignedGossipMessage}; use crate::ln::msgs::{UnsignedChannelAnnouncement, UnsignedGossipMessage};
use crate::ln::script::ShutdownScript; use crate::ln::script::ShutdownScript;
use crate::offers::invoice::UnsignedBolt12Invoice; use crate::offers::invoice::UnsignedBolt12Invoice;
use crate::offers::invoice_request::UnsignedInvoiceRequest;
use crate::types::payment::PaymentPreimage; use crate::types::payment::PaymentPreimage;
use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer}; use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer};
use crate::util::transaction_utils; use crate::util::transaction_utils;
@ -870,21 +869,6 @@ pub trait NodeSigner {
&self, invoice: &RawBolt11Invoice, recipient: Recipient, &self, invoice: &RawBolt11Invoice, recipient: Recipient,
) -> Result<RecoverableSignature, ()>; ) -> Result<RecoverableSignature, ()>;
/// Signs the [`TaggedHash`] of a BOLT 12 invoice request.
///
/// May be called by a function passed to [`UnsignedInvoiceRequest::sign`] where
/// `invoice_request` is the callee.
///
/// Implementors may check that the `invoice_request` is expected rather than blindly signing
/// the tagged hash. An `Ok` result should sign `invoice_request.tagged_hash().as_digest()` with
/// the node's signing key or an ephemeral key to preserve privacy, whichever is associated with
/// [`UnsignedInvoiceRequest::payer_signing_pubkey`].
///
/// [`TaggedHash`]: crate::offers::merkle::TaggedHash
fn sign_bolt12_invoice_request(
&self, invoice_request: &UnsignedInvoiceRequest,
) -> Result<schnorr::Signature, ()>;
/// Signs the [`TaggedHash`] of a BOLT 12 invoice. /// Signs the [`TaggedHash`] of a BOLT 12 invoice.
/// ///
/// May be called by a function passed to [`UnsignedBolt12Invoice::sign`] where `invoice` is the /// May be called by a function passed to [`UnsignedBolt12Invoice::sign`] where `invoice` is the
@ -2206,15 +2190,6 @@ impl NodeSigner for KeysManager {
Ok(self.secp_ctx.sign_ecdsa_recoverable(&hash_to_message!(&hash), secret)) Ok(self.secp_ctx.sign_ecdsa_recoverable(&hash_to_message!(&hash), secret))
} }
fn sign_bolt12_invoice_request(
&self, invoice_request: &UnsignedInvoiceRequest,
) -> Result<schnorr::Signature, ()> {
let message = invoice_request.tagged_hash().as_digest();
let keys = Keypair::from_secret_key(&self.secp_ctx, &self.node_secret);
let aux_rand = self.get_secure_random_bytes();
Ok(self.secp_ctx.sign_schnorr_with_aux_rand(message, &keys, &aux_rand))
}
fn sign_bolt12_invoice( fn sign_bolt12_invoice(
&self, invoice: &UnsignedBolt12Invoice, &self, invoice: &UnsignedBolt12Invoice,
) -> Result<schnorr::Signature, ()> { ) -> Result<schnorr::Signature, ()> {
@ -2384,12 +2359,6 @@ impl NodeSigner for PhantomKeysManager {
Ok(self.inner.secp_ctx.sign_ecdsa_recoverable(&hash_to_message!(&hash), secret)) Ok(self.inner.secp_ctx.sign_ecdsa_recoverable(&hash_to_message!(&hash), secret))
} }
fn sign_bolt12_invoice_request(
&self, invoice_request: &UnsignedInvoiceRequest,
) -> Result<schnorr::Signature, ()> {
self.inner.sign_bolt12_invoice_request(invoice_request)
}
fn sign_bolt12_invoice( fn sign_bolt12_invoice(
&self, invoice: &UnsignedBolt12Invoice, &self, invoice: &UnsignedBolt12Invoice,
) -> Result<schnorr::Signature, ()> { ) -> Result<schnorr::Signature, ()> {

View File

@ -34,7 +34,6 @@ use crate::ln::{msgs, wire};
use crate::ln::msgs::LightningError; use crate::ln::msgs::LightningError;
use crate::ln::script::ShutdownScript; use crate::ln::script::ShutdownScript;
use crate::offers::invoice::UnsignedBolt12Invoice; use crate::offers::invoice::UnsignedBolt12Invoice;
use crate::offers::invoice_request::UnsignedInvoiceRequest;
use crate::onion_message::messenger::{DefaultMessageRouter, Destination, MessageRouter, OnionMessagePath}; use crate::onion_message::messenger::{DefaultMessageRouter, Destination, MessageRouter, OnionMessagePath};
use crate::routing::gossip::{EffectiveCapacity, NetworkGraph, NodeId, RoutingFees}; use crate::routing::gossip::{EffectiveCapacity, NetworkGraph, NodeId, RoutingFees};
use crate::routing::utxo::{UtxoLookup, UtxoLookupError, UtxoResult}; use crate::routing::utxo::{UtxoLookup, UtxoLookupError, UtxoResult};
@ -1216,12 +1215,6 @@ impl NodeSigner for TestNodeSigner {
unreachable!() unreachable!()
} }
fn sign_bolt12_invoice_request(
&self, _invoice_request: &UnsignedInvoiceRequest
) -> Result<schnorr::Signature, ()> {
unreachable!()
}
fn sign_bolt12_invoice( fn sign_bolt12_invoice(
&self, _invoice: &UnsignedBolt12Invoice, &self, _invoice: &UnsignedBolt12Invoice,
) -> Result<schnorr::Signature, ()> { ) -> Result<schnorr::Signature, ()> {
@ -1269,12 +1262,6 @@ impl NodeSigner for TestKeysInterface {
self.backing.sign_invoice(invoice, recipient) self.backing.sign_invoice(invoice, recipient)
} }
fn sign_bolt12_invoice_request(
&self, invoice_request: &UnsignedInvoiceRequest
) -> Result<schnorr::Signature, ()> {
self.backing.sign_bolt12_invoice_request(invoice_request)
}
fn sign_bolt12_invoice( fn sign_bolt12_invoice(
&self, invoice: &UnsignedBolt12Invoice, &self, invoice: &UnsignedBolt12Invoice,
) -> Result<schnorr::Signature, ()> { ) -> Result<schnorr::Signature, ()> {