X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Foffers%2Finvoice.rs;h=ecb0c80061ba3851b3975373b099111f9eb4ebf0;hb=bccddcaabdca983bcfe8807d794458454fb350fc;hp=f665bd3b26e81dda6c2181e15dc2f46758f84a9f;hpb=3bd00b943a3f3a101a77ad127b219dad91845aa6;p=rust-lightning diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index f665bd3b..ecb0c800 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -1,4 +1,4 @@ -// This file is Copyright its original authors, visible in version control + // This file is Copyright its original authors, visible in version control // history. // // This file is licensed under the Apache License, Version 2.0 ( -//! |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) +//! .sign(|message: &UnsignedBolt12Invoice| +//! Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) //! ) //! .expect("failed verifying signature") //! .write(&mut buffer) @@ -90,8 +91,8 @@ //! .allow_mpp() //! .fallback_v0_p2wpkh(&wpubkey_hash) //! .build()? -//! .sign::<_, Infallible>( -//! |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) +//! .sign(|message: &UnsignedBolt12Invoice| +//! Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) //! ) //! .expect("failed verifying signature") //! .write(&mut buffer) @@ -103,23 +104,22 @@ use bitcoin::blockdata::constants::ChainHash; use bitcoin::hash_types::{WPubkeyHash, WScriptHash}; -use bitcoin::hashes::Hash; use bitcoin::network::constants::Network; use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, self}; use bitcoin::secp256k1::schnorr::Signature; use bitcoin::address::{Address, Payload, WitnessProgram, WitnessVersion}; use bitcoin::key::TweakedPublicKey; -use core::convert::{AsRef, Infallible, TryFrom}; use core::time::Duration; +use core::hash::{Hash, Hasher}; use crate::io; use crate::blinded_path::BlindedPath; -use crate::ln::PaymentHash; +use crate::ln::types::PaymentHash; use crate::ln::channelmanager::PaymentId; use crate::ln::features::{BlindedHopFeatures, Bolt12InvoiceFeatures, InvoiceRequestFeatures, OfferFeatures}; use crate::ln::inbound_payment::ExpandedKey; use crate::ln::msgs::DecodeError; use crate::offers::invoice_request::{INVOICE_REQUEST_PAYER_ID_TYPE, INVOICE_REQUEST_TYPES, IV_BYTES as INVOICE_REQUEST_IV_BYTES, InvoiceRequest, InvoiceRequestContents, InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef}; -use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, TlvStream, WithoutSignatures, self}; +use crate::offers::merkle::{SignError, SignFn, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, TlvStream, WithoutSignatures, self}; use crate::offers::offer::{Amount, OFFER_TYPES, OfferTlvStream, OfferTlvStreamRef, Quantity}; use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError, ParsedMessage}; use crate::offers::payer::{PAYER_METADATA_TYPE, PayerTlvStream, PayerTlvStreamRef}; @@ -128,6 +128,7 @@ use crate::offers::signer; use crate::util::ser::{HighZeroBytesDroppedBigSize, Iterable, SeekReadable, WithoutLength, Writeable, Writer}; use crate::util::string::PrintableString; +#[allow(unused_imports)] use crate::prelude::*; #[cfg(feature = "std")] @@ -209,10 +210,9 @@ macro_rules! invoice_explicit_signing_pubkey_builder_methods { ($self: ident, $s #[cfg_attr(c_bindings, allow(dead_code))] pub(super) fn for_offer( invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, - created_at: Duration, payment_hash: PaymentHash + created_at: Duration, payment_hash: PaymentHash, signing_pubkey: PublicKey ) -> Result { let amount_msats = Self::amount_msats(invoice_request)?; - let signing_pubkey = invoice_request.contents.inner.offer.signing_pubkey(); let contents = InvoiceContents::ForOffer { invoice_request: invoice_request.contents.clone(), fields: Self::fields( @@ -271,7 +271,7 @@ macro_rules! invoice_derived_signing_pubkey_builder_methods { ($self: ident, $se created_at: Duration, payment_hash: PaymentHash, keys: KeyPair ) -> Result { let amount_msats = Self::amount_msats(invoice_request)?; - let signing_pubkey = invoice_request.contents.inner.offer.signing_pubkey(); + let signing_pubkey = keys.public_key(); let contents = InvoiceContents::ForOffer { invoice_request: invoice_request.contents.clone(), fields: Self::fields( @@ -324,8 +324,8 @@ macro_rules! invoice_derived_signing_pubkey_builder_methods { ($self: ident, $se let mut unsigned_invoice = UnsignedBolt12Invoice::new(invreq_bytes, invoice.clone()); let invoice = unsigned_invoice - .sign::<_, Infallible>( - |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) + .sign(|message: &UnsignedBolt12Invoice| + Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) ) .unwrap(); Ok(invoice) @@ -389,6 +389,7 @@ macro_rules! invoice_builder_methods { ( /// Successive calls to this method will add another address. Caller is responsible for not /// adding duplicate addresses and only calling if capable of receiving to P2WSH addresses. pub fn fallback_v0_p2wsh($($self_mut)* $self: $self_type, script_hash: &WScriptHash) -> $return_type { + use bitcoin::hashes::Hash; let address = FallbackAddress { version: WitnessVersion::V0.to_num(), program: Vec::from(script_hash.to_byte_array()), @@ -402,6 +403,7 @@ macro_rules! invoice_builder_methods { ( /// Successive calls to this method will add another address. Caller is responsible for not /// adding duplicate addresses and only calling if capable of receiving to P2WPKH addresses. pub fn fallback_v0_p2wpkh($($self_mut)* $self: $self_type, pubkey_hash: &WPubkeyHash) -> $return_type { + use bitcoin::hashes::Hash; let address = FallbackAddress { version: WitnessVersion::V0.to_num(), program: Vec::from(pubkey_hash.to_byte_array()), @@ -501,12 +503,37 @@ for InvoiceBuilder<'a, DerivedSigningPubkey> { /// /// This is serialized as a TLV stream, which includes TLV records from the originating message. As /// such, it may include unknown, odd TLV records. +#[derive(Clone)] pub struct UnsignedBolt12Invoice { bytes: Vec, contents: InvoiceContents, tagged_hash: TaggedHash, } +/// A function for signing an [`UnsignedBolt12Invoice`]. +pub trait SignBolt12InvoiceFn { + /// Signs a [`TaggedHash`] computed over the merkle root of `message`'s TLV stream. + fn sign_invoice(&self, message: &UnsignedBolt12Invoice) -> Result; +} + +impl SignBolt12InvoiceFn for F +where + F: Fn(&UnsignedBolt12Invoice) -> Result, +{ + fn sign_invoice(&self, message: &UnsignedBolt12Invoice) -> Result { + self(message) + } +} + +impl SignFn for F +where + F: SignBolt12InvoiceFn, +{ + fn sign(&self, message: &UnsignedBolt12Invoice) -> Result { + self.sign_invoice(message) + } +} + impl UnsignedBolt12Invoice { fn new(invreq_bytes: &[u8], contents: InvoiceContents) -> Self { // Use the invoice_request bytes instead of the invoice_request TLV stream as the latter may @@ -519,7 +546,7 @@ impl UnsignedBolt12Invoice { let mut bytes = Vec::new(); unsigned_tlv_stream.write(&mut bytes).unwrap(); - let tagged_hash = TaggedHash::new(SIGNATURE_TAG, &bytes); + let tagged_hash = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &bytes); Self { bytes, contents, tagged_hash } } @@ -534,12 +561,9 @@ macro_rules! unsigned_invoice_sign_method { ($self: ident, $self_type: ty $(, $s /// Signs the [`TaggedHash`] of the invoice using the given function. /// /// Note: The hash computation may have included unknown, odd TLV records. - /// - /// This is not exported to bindings users as functions aren't currently mapped. - pub fn sign($($self_mut)* $self: $self_type, sign: F) -> Result> - where - F: FnOnce(&Self) -> Result - { + pub fn sign( + $($self_mut)* $self: $self_type, sign: F + ) -> Result { let pubkey = $self.contents.fields().signing_pubkey; let signature = merkle::sign_message(sign, &$self, pubkey)?; @@ -592,7 +616,6 @@ impl AsRef for UnsignedBolt12Invoice { /// [`Refund`]: crate::offers::refund::Refund /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest #[derive(Clone, Debug)] -#[cfg_attr(test, derive(PartialEq))] pub struct Bolt12Invoice { bytes: Vec, contents: InvoiceContents, @@ -675,7 +698,7 @@ macro_rules! invoice_accessors { ($self: ident, $contents: expr) => { /// /// [`Offer`]: crate::offers::offer::Offer /// [`Offer::amount`]: crate::offers::offer::Offer::amount - pub fn amount(&$self) -> Option<&Amount> { + pub fn amount(&$self) -> Option { $contents.amount() } @@ -695,7 +718,7 @@ macro_rules! invoice_accessors { ($self: ident, $contents: expr) => { /// From [`Offer::description`] or [`Refund::description`]. /// /// [`Offer::description`]: crate::offers::offer::Offer::description - pub fn description(&$self) -> PrintableString { + pub fn description(&$self) -> Option { $contents.description() } @@ -864,6 +887,20 @@ impl Bolt12Invoice { } } +impl PartialEq for Bolt12Invoice { + fn eq(&self, other: &Self) -> bool { + self.bytes.eq(&other.bytes) + } +} + +impl Eq for Bolt12Invoice {} + +impl Hash for Bolt12Invoice { + fn hash(&self, state: &mut H) { + self.bytes.hash(state); + } +} + impl InvoiceContents { /// Whether the original offer or refund has expired. #[cfg(feature = "std")] @@ -908,7 +945,7 @@ impl InvoiceContents { } } - fn amount(&self) -> Option<&Amount> { + fn amount(&self) -> Option { match self { InvoiceContents::ForOffer { invoice_request, .. } => invoice_request.inner.offer.amount(), @@ -916,12 +953,12 @@ impl InvoiceContents { } } - fn description(&self) -> PrintableString { + fn description(&self) -> Option { match self { InvoiceContents::ForOffer { invoice_request, .. } => { invoice_request.inner.offer.description() }, - InvoiceContents::ForRefund { refund, .. } => refund.description(), + InvoiceContents::ForRefund { refund, .. } => Some(refund.description()), } } @@ -1188,7 +1225,7 @@ impl TryFrom> for UnsignedBolt12Invoice { (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream) )?; - let tagged_hash = TaggedHash::new(SIGNATURE_TAG, &bytes); + let tagged_hash = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &bytes); Ok(UnsignedBolt12Invoice { bytes, contents, tagged_hash }) } @@ -1333,7 +1370,7 @@ impl TryFrom> for Bolt12Invoice { None => return Err(Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingSignature)), Some(signature) => signature, }; - let tagged_hash = TaggedHash::new(SIGNATURE_TAG, &bytes); + let tagged_hash = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &bytes); let pubkey = contents.fields().signing_pubkey; merkle::verify_signature(&signature, &tagged_hash, pubkey)?; @@ -1398,8 +1435,8 @@ impl TryFrom for InvoiceContents { features, signing_pubkey, }; - match offer_tlv_stream.node_id { - Some(expected_signing_pubkey) => { + match (offer_tlv_stream.node_id, &offer_tlv_stream.paths) { + (Some(expected_signing_pubkey), _) => { if fields.signing_pubkey != expected_signing_pubkey { return Err(Bolt12SemanticError::InvalidSigningPubkey); } @@ -1409,7 +1446,21 @@ impl TryFrom for InvoiceContents { )?; Ok(InvoiceContents::ForOffer { invoice_request, fields }) }, - None => { + (None, Some(paths)) => { + if !paths + .iter() + .filter_map(|path| path.blinded_hops.last()) + .any(|last_hop| fields.signing_pubkey == last_hop.blinded_node_id) + { + return Err(Bolt12SemanticError::InvalidSigningPubkey); + } + + let invoice_request = InvoiceRequestContents::try_from( + (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream) + )?; + Ok(InvoiceContents::ForOffer { invoice_request, fields }) + }, + (None, None) => { let refund = RefundContents::try_from( (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream) )?; @@ -1427,12 +1478,13 @@ mod tests { use bitcoin::blockdata::script::ScriptBuf; use bitcoin::hashes::Hash; use bitcoin::network::constants::Network; - use bitcoin::secp256k1::{Message, Secp256k1, XOnlyPublicKey, self}; + use bitcoin::secp256k1::{KeyPair, Message, Secp256k1, SecretKey, XOnlyPublicKey, self}; use bitcoin::address::{Address, Payload, WitnessProgram, WitnessVersion}; use bitcoin::key::TweakedPublicKey; - use core::convert::TryFrom; + use core::time::Duration; - use crate::blinded_path::{BlindedHop, BlindedPath}; + + use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode}; use crate::sign::KeyMaterial; use crate::ln::features::{Bolt12InvoiceFeatures, InvoiceRequestFeatures, OfferFeatures}; use crate::ln::inbound_payment::ExpandedKey; @@ -1440,6 +1492,7 @@ mod tests { use crate::offers::invoice_request::InvoiceRequestTlvStreamRef; use crate::offers::merkle::{SignError, SignatureTlvStreamRef, TaggedHash, self}; use crate::offers::offer::{Amount, OfferTlvStreamRef, Quantity}; + use crate::prelude::*; #[cfg(not(c_bindings))] use { crate::offers::offer::OfferBuilder, @@ -1477,7 +1530,7 @@ mod tests { let payment_paths = payment_paths(); let payment_hash = payment_hash(); let now = now(); - let unsigned_invoice = OfferBuilder::new("foo".into(), recipient_pubkey()) + let unsigned_invoice = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() @@ -1493,8 +1546,8 @@ mod tests { assert_eq!(unsigned_invoice.payer_metadata(), &[1; 32]); 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 })); - assert_eq!(unsigned_invoice.description(), PrintableString("foo")); + assert_eq!(unsigned_invoice.amount(), Some(Amount::Bitcoin { amount_msats: 1000 })); + assert_eq!(unsigned_invoice.description(), Some(PrintableString(""))); assert_eq!(unsigned_invoice.offer_features(), Some(&OfferFeatures::empty())); assert_eq!(unsigned_invoice.absolute_expiry(), None); assert_eq!(unsigned_invoice.message_paths(), &[]); @@ -1537,8 +1590,8 @@ mod tests { assert_eq!(invoice.payer_metadata(), &[1; 32]); 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 })); - assert_eq!(invoice.description(), PrintableString("foo")); + assert_eq!(invoice.amount(), Some(Amount::Bitcoin { amount_msats: 1000 })); + assert_eq!(invoice.description(), Some(PrintableString(""))); assert_eq!(invoice.offer_features(), Some(&OfferFeatures::empty())); assert_eq!(invoice.absolute_expiry(), None); assert_eq!(invoice.message_paths(), &[]); @@ -1562,7 +1615,7 @@ mod tests { assert_eq!(invoice.invoice_features(), &Bolt12InvoiceFeatures::empty()); assert_eq!(invoice.signing_pubkey(), recipient_pubkey()); - let message = TaggedHash::new(SIGNATURE_TAG, &invoice.bytes); + let message = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &invoice.bytes); assert!(merkle::verify_signature(&invoice.signature, &message, recipient_pubkey()).is_ok()); let digest = Message::from_slice(&invoice.signable_hash()).unwrap(); @@ -1579,7 +1632,7 @@ mod tests { metadata: None, currency: None, amount: Some(1000), - description: Some(&String::from("foo")), + description: Some(&String::from("")), features: None, absolute_expiry: None, paths: None, @@ -1594,6 +1647,7 @@ mod tests { quantity: None, payer_id: Some(&payer_pubkey()), payer_note: None, + paths: None, }, InvoiceTlvStreamRef { paths: Some(Iterable(payment_paths.iter().map(|(_, path)| path))), @@ -1620,7 +1674,7 @@ mod tests { let payment_paths = payment_paths(); let payment_hash = payment_hash(); let now = now(); - let invoice = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap() + let invoice = RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap() .build().unwrap() .respond_with_no_std(payment_paths.clone(), payment_hash, recipient_pubkey(), now) .unwrap() @@ -1635,7 +1689,7 @@ mod tests { assert_eq!(invoice.offer_chains(), None); assert_eq!(invoice.metadata(), None); assert_eq!(invoice.amount(), None); - assert_eq!(invoice.description(), PrintableString("foo")); + assert_eq!(invoice.description(), Some(PrintableString(""))); assert_eq!(invoice.offer_features(), None); assert_eq!(invoice.absolute_expiry(), None); assert_eq!(invoice.message_paths(), &[]); @@ -1659,7 +1713,7 @@ mod tests { assert_eq!(invoice.invoice_features(), &Bolt12InvoiceFeatures::empty()); assert_eq!(invoice.signing_pubkey(), recipient_pubkey()); - let message = TaggedHash::new(SIGNATURE_TAG, &invoice.bytes); + let message = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &invoice.bytes); assert!(merkle::verify_signature(&invoice.signature, &message, recipient_pubkey()).is_ok()); assert_eq!( @@ -1671,7 +1725,7 @@ mod tests { metadata: None, currency: None, amount: None, - description: Some(&String::from("foo")), + description: Some(&String::from("")), features: None, absolute_expiry: None, paths: None, @@ -1686,6 +1740,7 @@ mod tests { quantity: None, payer_id: Some(&payer_pubkey()), payer_note: None, + paths: None, }, InvoiceTlvStreamRef { paths: Some(Iterable(payment_paths.iter().map(|(_, path)| path))), @@ -1713,7 +1768,7 @@ mod tests { let future_expiry = Duration::from_secs(u64::max_value()); let past_expiry = Duration::from_secs(0); - if let Err(e) = OfferBuilder::new("foo".into(), recipient_pubkey()) + if let Err(e) = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .absolute_expiry(future_expiry) .build().unwrap() @@ -1727,7 +1782,7 @@ mod tests { panic!("error building invoice: {:?}", e); } - match OfferBuilder::new("foo".into(), recipient_pubkey()) + match OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .absolute_expiry(past_expiry) .build().unwrap() @@ -1749,7 +1804,7 @@ mod tests { let future_expiry = Duration::from_secs(u64::max_value()); let past_expiry = Duration::from_secs(0); - if let Err(e) = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap() + if let Err(e) = RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap() .absolute_expiry(future_expiry) .build().unwrap() .respond_with(payment_paths(), payment_hash(), recipient_pubkey()) @@ -1759,7 +1814,7 @@ mod tests { panic!("error building invoice: {:?}", e); } - match RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap() + match RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap() .absolute_expiry(past_expiry) .build().unwrap() .respond_with(payment_paths(), payment_hash(), recipient_pubkey()) @@ -1773,14 +1828,13 @@ mod tests { #[test] fn builds_invoice_from_offer_using_derived_keys() { - let desc = "foo".to_string(); let node_id = recipient_pubkey(); let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); let entropy = FixedEntropy {}; let secp_ctx = Secp256k1::new(); let blinded_path = BlindedPath { - introduction_node_id: pubkey(40), + introduction_node: IntroductionNode::NodeId(pubkey(40)), blinding_point: pubkey(41), blinded_hops: vec![ BlindedHop { blinded_node_id: pubkey(42), encrypted_payload: vec![0; 43] }, @@ -1791,7 +1845,7 @@ mod tests { #[cfg(c_bindings)] use crate::offers::offer::OfferWithDerivedMetadataBuilder as OfferBuilder; let offer = OfferBuilder - ::deriving_signing_pubkey(desc, node_id, &expanded_key, &entropy, &secp_ctx) + ::deriving_signing_pubkey(node_id, &expanded_key, &entropy, &secp_ctx) .amount_msats(1000) .path(blinded_path) .build().unwrap(); @@ -1810,9 +1864,8 @@ mod tests { let expanded_key = ExpandedKey::new(&KeyMaterial([41; 32])); assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_err()); - let desc = "foo".to_string(); let offer = OfferBuilder - ::deriving_signing_pubkey(desc, node_id, &expanded_key, &entropy, &secp_ctx) + ::deriving_signing_pubkey(node_id, &expanded_key, &entropy, &secp_ctx) .amount_msats(1000) // Omit the path so that node_id is used for the signing pubkey instead of deriving .build().unwrap(); @@ -1835,7 +1888,7 @@ mod tests { let entropy = FixedEntropy {}; let secp_ctx = Secp256k1::new(); - let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap() + let refund = RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap() .build().unwrap(); if let Err(e) = refund @@ -1854,7 +1907,7 @@ mod tests { let now = now(); let one_hour = Duration::from_secs(3600); - let invoice = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() @@ -1870,7 +1923,7 @@ mod tests { assert_eq!(invoice.relative_expiry(), one_hour); assert_eq!(tlv_stream.relative_expiry, Some(one_hour.as_secs() as u32)); - let invoice = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() @@ -1889,7 +1942,7 @@ mod tests { #[test] fn builds_invoice_with_amount_from_request() { - let invoice = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() @@ -1906,7 +1959,7 @@ mod tests { #[test] fn builds_invoice_with_quantity_from_request() { - let invoice = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .supported_quantity(Quantity::Unbounded) .build().unwrap() @@ -1921,7 +1974,7 @@ mod tests { assert_eq!(invoice.amount_msats(), 2000); assert_eq!(tlv_stream.amount, Some(2000)); - match OfferBuilder::new("foo".into(), recipient_pubkey()) + match OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .supported_quantity(Quantity::Unbounded) .build().unwrap() @@ -1943,7 +1996,7 @@ mod tests { let x_only_pubkey = XOnlyPublicKey::from_keypair(&recipient_keys()).0; let tweaked_pubkey = TweakedPublicKey::dangerous_assume_tweaked(x_only_pubkey); - let invoice = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() @@ -1988,7 +2041,7 @@ mod tests { let mut features = Bolt12InvoiceFeatures::empty(); features.set_basic_mpp_optional(); - let invoice = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() @@ -2005,7 +2058,7 @@ mod tests { #[test] fn fails_signing_invoice() { - match OfferBuilder::new("foo".into(), recipient_pubkey()) + match OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() @@ -2013,13 +2066,13 @@ mod tests { .sign(payer_sign).unwrap() .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .build().unwrap() - .sign(|_| Err(())) + .sign(fail_sign) { Ok(_) => panic!("expected error"), - Err(e) => assert_eq!(e, SignError::Signing(())), + Err(e) => assert_eq!(e, SignError::Signing), } - match OfferBuilder::new("foo".into(), recipient_pubkey()) + match OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() @@ -2036,7 +2089,7 @@ mod tests { #[test] fn parses_invoice_with_payment_paths() { - let invoice = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() @@ -2091,7 +2144,7 @@ mod tests { #[test] fn parses_invoice_with_created_at() { - let invoice = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() @@ -2121,7 +2174,7 @@ mod tests { #[test] fn parses_invoice_with_relative_expiry() { - let invoice = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() @@ -2143,7 +2196,7 @@ mod tests { #[test] fn parses_invoice_with_payment_hash() { - let invoice = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() @@ -2173,7 +2226,7 @@ mod tests { #[test] fn parses_invoice_with_amount() { - let invoice = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() @@ -2201,7 +2254,7 @@ mod tests { #[test] fn parses_invoice_with_allow_mpp() { - let invoice = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() @@ -2232,7 +2285,7 @@ mod tests { let x_only_pubkey = XOnlyPublicKey::from_keypair(&recipient_keys()).0; let tweaked_pubkey = TweakedPublicKey::dangerous_assume_tweaked(x_only_pubkey); - let offer = OfferBuilder::new("foo".into(), recipient_pubkey()) + let offer = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap(); let invoice_request = offer @@ -2287,7 +2340,7 @@ mod tests { #[test] fn parses_invoice_with_node_id() { - let invoice = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() @@ -2326,10 +2379,85 @@ mod tests { } } + #[test] + fn parses_invoice_with_node_id_from_blinded_path() { + let paths = vec![ + BlindedPath { + introduction_node: IntroductionNode::NodeId(pubkey(40)), + blinding_point: pubkey(41), + blinded_hops: vec![ + BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] }, + BlindedHop { blinded_node_id: pubkey(44), encrypted_payload: vec![0; 44] }, + ], + }, + BlindedPath { + introduction_node: IntroductionNode::NodeId(pubkey(40)), + blinding_point: pubkey(41), + blinded_hops: vec![ + BlindedHop { blinded_node_id: pubkey(45), encrypted_payload: vec![0; 45] }, + BlindedHop { blinded_node_id: pubkey(46), encrypted_payload: vec![0; 46] }, + ], + }, + ]; + + let blinded_node_id_sign = |message: &UnsignedBolt12Invoice| { + let secp_ctx = Secp256k1::new(); + let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[46; 32]).unwrap()); + Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) + }; + + let invoice = OfferBuilder::new(recipient_pubkey()) + .clear_signing_pubkey() + .amount_msats(1000) + .path(paths[0].clone()) + .path(paths[1].clone()) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .build().unwrap() + .sign(payer_sign).unwrap() + .respond_with_no_std_using_signing_pubkey( + payment_paths(), payment_hash(), now(), pubkey(46) + ).unwrap() + .build().unwrap() + .sign(blinded_node_id_sign).unwrap(); + + let mut buffer = Vec::new(); + invoice.write(&mut buffer).unwrap(); + + if let Err(e) = Bolt12Invoice::try_from(buffer) { + panic!("error parsing invoice: {:?}", e); + } + + let invoice = OfferBuilder::new(recipient_pubkey()) + .clear_signing_pubkey() + .amount_msats(1000) + .path(paths[0].clone()) + .path(paths[1].clone()) + .build().unwrap() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .build().unwrap() + .sign(payer_sign).unwrap() + .respond_with_no_std_using_signing_pubkey( + payment_paths(), payment_hash(), now(), recipient_pubkey() + ).unwrap() + .build().unwrap() + .sign(recipient_sign).unwrap(); + + let mut buffer = Vec::new(); + invoice.write(&mut buffer).unwrap(); + + match Bolt12Invoice::try_from(buffer) { + Ok(_) => panic!("expected error"), + Err(e) => { + assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::InvalidSigningPubkey)); + }, + } + } + #[test] fn fails_parsing_invoice_without_signature() { let mut buffer = Vec::new(); - OfferBuilder::new("foo".into(), recipient_pubkey()) + OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() @@ -2348,7 +2476,7 @@ mod tests { #[test] fn fails_parsing_invoice_with_invalid_signature() { - let mut invoice = OfferBuilder::new("foo".into(), recipient_pubkey()) + let mut invoice = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() @@ -2373,7 +2501,7 @@ mod tests { #[test] fn fails_parsing_invoice_with_extra_tlv_records() { - let invoice = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap()