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.
This commit is contained in:
Jeffrey Czyz 2024-08-20 15:52:17 -05:00
parent ad487e7232
commit d596b4e635
No known key found for this signature in database
GPG key ID: 912EF12EA67705F5
7 changed files with 635 additions and 670 deletions

View file

@ -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<Out: test_logger::Output>(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<UnsignedInvoiceRequest, Bolt12SemanticError> {
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<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() {
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<Out: test_logger::Output>(data: &[u8], out: Out) {

View file

@ -9612,7 +9612,7 @@ where
let nonce = Nonce::from_entropy_source(entropy);
let builder: InvoiceRequestBuilder<DerivedPayerSigningPubkey, 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();
let builder = builder.chain_hash(self.chain_hash)?;

View file

@ -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()

View file

@ -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();

File diff suppressed because it is too large Load diff

View file

@ -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(&<Vec<u8>>::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)

View file

@ -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<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))]
impl Offer {
request_invoice_derived_signing_pubkey!(self, InvoiceRequestBuilder<'a, 'b, DerivedPayerSigningPubkey, T>);
request_invoice_explicit_signing_pubkey!(self, InvoiceRequestBuilder<ExplicitPayerSigningPubkey, secp256k1::SignOnly>);
}
#[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),