X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Foffers%2Finvoice_request.rs;h=cdf94a2e80d5bee168deea35fdb03c817718cdc7;hb=1e580668684d4dbf11d69d75e5d4a5c4f8cc40bf;hp=792214d5250e340dfe40f54fe967678740a25b01;hpb=af318b6efb1398099fc9c42f630b74abcc0b4fba;p=rust-lightning diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index 792214d5..cdf94a2e 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -23,16 +23,16 @@ //! extern crate bitcoin; //! extern crate lightning; //! -//! use bitcoin::network::constants::Network; -//! use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey}; -//! use core::convert::Infallible; +//! use bitcoin::network::Network; +//! use bitcoin::secp256k1::{Keypair, PublicKey, Secp256k1, SecretKey}; //! use lightning::ln::features::OfferFeatures; +//! use lightning::offers::invoice_request::UnsignedInvoiceRequest; //! use lightning::offers::offer::Offer; //! use lightning::util::ser::Writeable; //! //! # fn parse() -> Result<(), lightning::offers::parse::Bolt12ParseError> { //! let secp_ctx = Secp256k1::new(); -//! let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32])?); +//! let keys = Keypair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32])?); //! let pubkey = PublicKey::from(keys); //! let mut buffer = Vec::new(); //! @@ -47,8 +47,8 @@ //! .quantity(5)? //! .payer_note("foo".to_string()) //! .build()? -//! .sign::<_, Infallible>( -//! |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) +//! .sign(|message: &UnsignedInvoiceRequest| +//! Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) //! ) //! .expect("failed verifying signature") //! .write(&mut buffer) @@ -58,28 +58,37 @@ //! ``` use bitcoin::blockdata::constants::ChainHash; -use bitcoin::network::constants::Network; -use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, self}; +use bitcoin::network::Network; +use bitcoin::secp256k1::{Keypair, PublicKey, Secp256k1, self}; use bitcoin::secp256k1::schnorr::Signature; -use core::convert::{AsRef, Infallible, TryFrom}; use core::ops::Deref; use crate::sign::EntropySource; 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::InvoiceRequestFeatures; use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce}; use crate::ln::msgs::DecodeError; -use crate::offers::invoice::{BlindedPayInfo, DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder}; -use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, self}; -use crate::offers::offer::{Offer, OfferContents, OfferTlvStream, OfferTlvStreamRef}; +use crate::offers::invoice::BlindedPayInfo; +use crate::offers::merkle::{SignError, SignFn, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, self}; +use crate::offers::offer::{Offer, OfferContents, OfferId, OfferTlvStream, OfferTlvStreamRef}; use crate::offers::parse::{Bolt12ParseError, ParsedMessage, Bolt12SemanticError}; use crate::offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef}; use crate::offers::signer::{Metadata, MetadataMaterial}; -use crate::util::ser::{HighZeroBytesDroppedBigSize, SeekReadable, WithoutLength, Writeable, Writer}; -use crate::util::string::PrintableString; +use crate::util::ser::{HighZeroBytesDroppedBigSize, Readable, SeekReadable, WithoutLength, Writeable, Writer}; +use crate::util::string::{PrintableString, UntrustedString}; +#[cfg(not(c_bindings))] +use { + crate::offers::invoice::{DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder}, +}; +#[cfg(c_bindings)] +use { + crate::offers::invoice::{InvoiceWithDerivedSigningPubkeyBuilder, InvoiceWithExplicitSigningPubkeyBuilder}, +}; + +#[allow(unused_imports)] use crate::prelude::*; /// Tag for the hash function used when signing an [`InvoiceRequest`]'s merkle root. @@ -218,8 +227,8 @@ macro_rules! invoice_request_derived_payer_id_builder_methods { ( let secp_ctx = secp_ctx.unwrap(); let keys = keys.unwrap(); let invoice_request = unsigned_invoice_request - .sign::<_, Infallible>( - |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) + .sign(|message: &UnsignedInvoiceRequest| + Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) ) .unwrap(); Ok(invoice_request) @@ -294,7 +303,7 @@ macro_rules! invoice_request_builder_methods { ( } fn build_with_checks($($self_mut)* $self: $self_type) -> Result< - (UnsignedInvoiceRequest, Option, Option<&'b Secp256k1<$secp_context>>), + (UnsignedInvoiceRequest, Option, Option<&'b Secp256k1<$secp_context>>), Bolt12SemanticError > { #[cfg(feature = "std")] { @@ -325,7 +334,7 @@ macro_rules! invoice_request_builder_methods { ( } fn build_without_checks($($self_mut)* $self: $self_type) -> - (UnsignedInvoiceRequest, Option, Option<&'b Secp256k1<$secp_context>>) + (UnsignedInvoiceRequest, Option, Option<&'b Secp256k1<$secp_context>>) { // Create the metadata for stateless verification of a Bolt12Invoice. let mut keys = None; @@ -478,12 +487,37 @@ for InvoiceRequestBuilder<'a, 'b, DerivedPayerId, secp256k1::All> { /// /// 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 UnsignedInvoiceRequest { bytes: Vec, contents: InvoiceRequestContents, tagged_hash: TaggedHash, } +/// A function for signing an [`UnsignedInvoiceRequest`]. +pub trait SignInvoiceRequestFn { + /// Signs a [`TaggedHash`] computed over the merkle root of `message`'s TLV stream. + fn sign_invoice_request(&self, message: &UnsignedInvoiceRequest) -> Result; +} + +impl SignInvoiceRequestFn for F +where + F: Fn(&UnsignedInvoiceRequest) -> Result, +{ + fn sign_invoice_request(&self, message: &UnsignedInvoiceRequest) -> Result { + self(message) + } +} + +impl SignFn for F +where + F: SignInvoiceRequestFn, +{ + fn sign(&self, message: &UnsignedInvoiceRequest) -> Result { + self.sign_invoice_request(message) + } +} + impl UnsignedInvoiceRequest { fn new(offer: &Offer, contents: InvoiceRequestContents) -> Self { // Use the offer bytes instead of the offer TLV stream as the offer may have contained @@ -496,7 +530,7 @@ impl UnsignedInvoiceRequest { 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 } } @@ -513,12 +547,9 @@ macro_rules! unsigned_invoice_request_sign_method { ( /// Signs the [`TaggedHash`] of the invoice request using the given function. /// /// Note: The hash computation may have included unknown, odd TLV records. - /// - /// This is not exported to bindings users as functions are not yet 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.payer_id; let signature = merkle::sign_message(sign, &$self, pubkey)?; @@ -577,6 +608,9 @@ pub struct InvoiceRequest { /// ways to respond depending on whether the signing keys were derived. #[derive(Clone, Debug)] pub struct VerifiedInvoiceRequest { + /// The identifier of the [`Offer`] for which the [`InvoiceRequest`] was made. + pub offer_id: OfferId, + /// The verified request. inner: InvoiceRequest, @@ -588,7 +622,7 @@ pub struct VerifiedInvoiceRequest { /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice /// [`respond_using_derived_keys`]: Self::respond_using_derived_keys /// [`respond_with`]: Self::respond_with - pub keys: Option, + pub keys: Option, } /// The contents of an [`InvoiceRequest`], which may be shared with an [`Bolt12Invoice`]. @@ -662,35 +696,25 @@ impl UnsignedInvoiceRequest { invoice_request_accessors!(self, self.contents); } -impl InvoiceRequest { - offer_accessors!(self, self.contents.inner.offer); - invoice_request_accessors!(self, self.contents); - - /// Signature of the invoice request using [`payer_id`]. - /// - /// [`payer_id`]: Self::payer_id - pub fn signature(&self) -> Signature { - self.signature - } - +macro_rules! invoice_request_respond_with_explicit_signing_pubkey_methods { ( + $self: ident, $contents: expr, $builder: ty +) => { /// Creates an [`InvoiceBuilder`] for the request with the given required fields and using the /// [`Duration`] since [`std::time::SystemTime::UNIX_EPOCH`] as the creation time. /// /// See [`InvoiceRequest::respond_with_no_std`] for further details where the aforementioned /// creation time is used for the `created_at` parameter. /// - /// This is not exported to bindings users as builder patterns don't map outside of move semantics. - /// /// [`Duration`]: core::time::Duration #[cfg(feature = "std")] pub fn respond_with( - &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash - ) -> Result, Bolt12SemanticError> { + &$self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash + ) -> Result<$builder, Bolt12SemanticError> { let created_at = std::time::SystemTime::now() .duration_since(std::time::SystemTime::UNIX_EPOCH) .expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH"); - self.respond_with_no_std(payment_paths, payment_hash, created_at) + $contents.respond_with_no_std(payment_paths, payment_hash, created_at) } /// Creates an [`InvoiceBuilder`] for the request with the given required fields. @@ -714,36 +738,93 @@ impl InvoiceRequest { /// If the originating [`Offer`] was created using [`OfferBuilder::deriving_signing_pubkey`], /// then use [`InvoiceRequest::verify`] and [`VerifiedInvoiceRequest`] methods instead. /// - /// This is not exported to bindings users as builder patterns don't map outside of move semantics. - /// /// [`Bolt12Invoice::created_at`]: crate::offers::invoice::Bolt12Invoice::created_at /// [`OfferBuilder::deriving_signing_pubkey`]: crate::offers::offer::OfferBuilder::deriving_signing_pubkey pub fn respond_with_no_std( - &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash, + &$self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash, created_at: core::time::Duration - ) -> Result, Bolt12SemanticError> { - if self.invoice_request_features().requires_unknown_bits() { + ) -> Result<$builder, Bolt12SemanticError> { + if $contents.invoice_request_features().requires_unknown_bits() { return Err(Bolt12SemanticError::UnknownRequiredFeatures); } - InvoiceBuilder::for_offer(self, payment_paths, created_at, payment_hash) + let signing_pubkey = match $contents.contents.inner.offer.signing_pubkey() { + Some(signing_pubkey) => signing_pubkey, + None => return Err(Bolt12SemanticError::MissingSigningPubkey), + }; + + <$builder>::for_offer(&$contents, payment_paths, created_at, payment_hash, signing_pubkey) } + #[cfg(test)] + #[allow(dead_code)] + pub(super) fn respond_with_no_std_using_signing_pubkey( + &$self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash, + created_at: core::time::Duration, signing_pubkey: PublicKey + ) -> Result<$builder, Bolt12SemanticError> { + debug_assert!($contents.contents.inner.offer.signing_pubkey().is_none()); + + if $contents.invoice_request_features().requires_unknown_bits() { + return Err(Bolt12SemanticError::UnknownRequiredFeatures); + } + + <$builder>::for_offer(&$contents, payment_paths, created_at, payment_hash, signing_pubkey) + } +} } + +macro_rules! invoice_request_verify_method { ($self: ident, $self_type: ty) => { /// Verifies that the request was for an offer created using the given key. Returns the verified /// request which contains the derived keys needed to sign a [`Bolt12Invoice`] for the request /// if they could be extracted from the metadata. /// /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice - pub fn verify( - self, key: &ExpandedKey, secp_ctx: &Secp256k1 + pub fn verify< + #[cfg(not(c_bindings))] + T: secp256k1::Signing + >( + $self: $self_type, key: &ExpandedKey, + #[cfg(not(c_bindings))] + secp_ctx: &Secp256k1, + #[cfg(c_bindings)] + secp_ctx: &Secp256k1, ) -> Result { - let keys = self.contents.inner.offer.verify(&self.bytes, key, secp_ctx)?; + let (offer_id, keys) = $self.contents.inner.offer.verify(&$self.bytes, key, secp_ctx)?; Ok(VerifiedInvoiceRequest { - inner: self, + offer_id, + #[cfg(not(c_bindings))] + inner: $self, + #[cfg(c_bindings)] + inner: $self.clone(), keys, }) } +} } + +#[cfg(not(c_bindings))] +impl InvoiceRequest { + offer_accessors!(self, self.contents.inner.offer); + invoice_request_accessors!(self, self.contents); + invoice_request_respond_with_explicit_signing_pubkey_methods!(self, self, InvoiceBuilder); + invoice_request_verify_method!(self, Self); +} + +#[cfg(c_bindings)] +impl InvoiceRequest { + offer_accessors!(self, self.contents.inner.offer); + invoice_request_accessors!(self, self.contents); + invoice_request_respond_with_explicit_signing_pubkey_methods!(self, self, InvoiceWithExplicitSigningPubkeyBuilder); + invoice_request_verify_method!(self, &Self); +} + +impl InvoiceRequest { + /// Signature of the invoice request using [`payer_id`]. + /// + /// [`payer_id`]: Self::payer_id + pub fn signature(&self) -> Signature { + self.signature + } + pub(crate) fn as_tlv_stream(&self) -> FullInvoiceRequestTlvStreamRef { let (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream) = self.contents.as_tlv_stream(); @@ -754,55 +835,25 @@ impl InvoiceRequest { } } -impl VerifiedInvoiceRequest { - offer_accessors!(self, self.inner.contents.inner.offer); - invoice_request_accessors!(self, self.inner.contents); - - /// Creates an [`InvoiceBuilder`] for the request with the given required fields and using the - /// [`Duration`] since [`std::time::SystemTime::UNIX_EPOCH`] as the creation time. - /// - /// See [`InvoiceRequest::respond_with_no_std`] for further details. - /// - /// This is not exported to bindings users as builder patterns don't map outside of move semantics. - /// - /// [`Duration`]: core::time::Duration - #[cfg(feature = "std")] - pub fn respond_with( - &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash - ) -> Result, Bolt12SemanticError> { - self.inner.respond_with(payment_paths, payment_hash) - } - - /// Creates an [`InvoiceBuilder`] for the request with the given required fields. - /// - /// See [`InvoiceRequest::respond_with_no_std`] for further details. - /// - /// This is not exported to bindings users as builder patterns don't map outside of move semantics. - pub fn respond_with_no_std( - &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash, - created_at: core::time::Duration - ) -> Result, Bolt12SemanticError> { - self.inner.respond_with_no_std(payment_paths, payment_hash, created_at) - } - +macro_rules! invoice_request_respond_with_derived_signing_pubkey_methods { ( + $self: ident, $contents: expr, $builder: ty +) => { /// Creates an [`InvoiceBuilder`] for the request using the given required fields and that uses /// derived signing keys from the originating [`Offer`] to sign the [`Bolt12Invoice`]. Must use /// the same [`ExpandedKey`] as the one used to create the offer. /// /// See [`InvoiceRequest::respond_with`] for further details. /// - /// This is not exported to bindings users as builder patterns don't map outside of move semantics. - /// /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice #[cfg(feature = "std")] pub fn respond_using_derived_keys( - &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash - ) -> Result, Bolt12SemanticError> { + &$self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash + ) -> Result<$builder, Bolt12SemanticError> { let created_at = std::time::SystemTime::now() .duration_since(std::time::SystemTime::UNIX_EPOCH) .expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH"); - self.respond_using_derived_keys_no_std(payment_paths, payment_hash, created_at) + $self.respond_using_derived_keys_no_std(payment_paths, payment_hash, created_at) } /// Creates an [`InvoiceBuilder`] for the request using the given required fields and that uses @@ -811,26 +862,58 @@ impl VerifiedInvoiceRequest { /// /// See [`InvoiceRequest::respond_with_no_std`] for further details. /// - /// This is not exported to bindings users as builder patterns don't map outside of move semantics. - /// /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice pub fn respond_using_derived_keys_no_std( - &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash, + &$self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash, created_at: core::time::Duration - ) -> Result, Bolt12SemanticError> { - if self.inner.invoice_request_features().requires_unknown_bits() { + ) -> Result<$builder, Bolt12SemanticError> { + if $self.inner.invoice_request_features().requires_unknown_bits() { return Err(Bolt12SemanticError::UnknownRequiredFeatures); } - let keys = match self.keys { + let keys = match $self.keys { None => return Err(Bolt12SemanticError::InvalidMetadata), Some(keys) => keys, }; - InvoiceBuilder::for_offer_using_keys( - &self.inner, payment_paths, created_at, payment_hash, keys + match $contents.contents.inner.offer.signing_pubkey() { + Some(signing_pubkey) => debug_assert_eq!(signing_pubkey, keys.public_key()), + None => return Err(Bolt12SemanticError::MissingSigningPubkey), + } + + <$builder>::for_offer_using_keys( + &$self.inner, payment_paths, created_at, payment_hash, keys ) } +} } + +impl VerifiedInvoiceRequest { + offer_accessors!(self, self.inner.contents.inner.offer); + invoice_request_accessors!(self, self.inner.contents); + #[cfg(not(c_bindings))] + invoice_request_respond_with_explicit_signing_pubkey_methods!(self, self.inner, InvoiceBuilder); + #[cfg(c_bindings)] + invoice_request_respond_with_explicit_signing_pubkey_methods!(self, self.inner, InvoiceWithExplicitSigningPubkeyBuilder); + #[cfg(not(c_bindings))] + invoice_request_respond_with_derived_signing_pubkey_methods!(self, self.inner, InvoiceBuilder); + #[cfg(c_bindings)] + invoice_request_respond_with_derived_signing_pubkey_methods!(self, self.inner, InvoiceWithDerivedSigningPubkeyBuilder); + + pub(crate) fn fields(&self) -> InvoiceRequestFields { + let InvoiceRequestContents { + payer_id, + inner: InvoiceRequestContentsWithoutPayerId { + payer: _, offer: _, chain: _, amount_msats: _, features: _, quantity, payer_note + }, + } = &self.inner.contents; + + InvoiceRequestFields { + payer_id: *payer_id, + quantity: *quantity, + payer_note_truncated: payer_note.clone() + .map(|mut s| { s.truncate(PAYER_NOTE_LIMIT); UntrustedString(s) }), + } + } } impl InvoiceRequestContents { @@ -902,6 +985,7 @@ impl InvoiceRequestContentsWithoutPayerId { quantity: self.quantity, payer_id: None, payer_note: self.payer_note.as_ref(), + paths: None, }; (payer, offer, invoice_request) @@ -934,6 +1018,8 @@ pub(super) const INVOICE_REQUEST_TYPES: core::ops::Range = 80..160; /// [`Refund::payer_id`]: crate::offers::refund::Refund::payer_id pub(super) const INVOICE_REQUEST_PAYER_ID_TYPE: u64 = 88; +// This TLV stream is used for both InvoiceRequest and Refund, but not all TLV records are valid for +// InvoiceRequest as noted below. tlv_stream!(InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef, INVOICE_REQUEST_TYPES, { (80, chain: ChainHash), (82, amount: (u64, HighZeroBytesDroppedBigSize)), @@ -941,6 +1027,8 @@ tlv_stream!(InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef, INVOICE_REQUEST (86, quantity: (u64, HighZeroBytesDroppedBigSize)), (INVOICE_REQUEST_PAYER_ID_TYPE, payer_id: PublicKey), (89, payer_note: (String, WithoutLength)), + // Only used for Refund since the onion message of an InvoiceRequest has a reply path. + (90, paths: (Vec, WithoutLength)), }); type FullInvoiceRequestTlvStream = @@ -985,7 +1073,7 @@ impl TryFrom> for UnsignedInvoiceRequest { (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream) )?; - let tagged_hash = TaggedHash::new(SIGNATURE_TAG, &bytes); + let tagged_hash = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &bytes); Ok(UnsignedInvoiceRequest { bytes, contents, tagged_hash }) } @@ -1009,7 +1097,7 @@ impl TryFrom> for InvoiceRequest { None => return Err(Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingSignature)), Some(signature) => signature, }; - let message = TaggedHash::new(SIGNATURE_TAG, &bytes); + let message = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &bytes); merkle::verify_signature(&signature, &message, contents.payer_id)?; Ok(InvoiceRequest { bytes, contents, signature }) @@ -1023,7 +1111,9 @@ impl TryFrom for InvoiceRequestContents { let ( PayerTlvStream { metadata }, offer_tlv_stream, - InvoiceRequestTlvStream { chain, amount, features, quantity, payer_id, payer_note }, + InvoiceRequestTlvStream { + chain, amount, features, quantity, payer_id, payer_note, paths, + }, ) = tlv_stream; let payer = match metadata { @@ -1050,6 +1140,10 @@ impl TryFrom for InvoiceRequestContents { Some(payer_id) => payer_id, }; + if paths.is_some() { + return Err(Bolt12SemanticError::UnexpectedPaths); + } + Ok(InvoiceRequestContents { inner: InvoiceRequestContentsWithoutPayerId { payer, offer, chain, amount_msats: amount, features, quantity, payer_note, @@ -1059,14 +1153,59 @@ impl TryFrom for InvoiceRequestContents { } } +/// Fields sent in an [`InvoiceRequest`] message to include in [`PaymentContext::Bolt12Offer`]. +/// +/// [`PaymentContext::Bolt12Offer`]: crate::blinded_path::payment::PaymentContext::Bolt12Offer +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct InvoiceRequestFields { + /// A possibly transient pubkey used to sign the invoice request. + pub payer_id: PublicKey, + + /// The quantity of the offer's item conforming to [`Offer::is_valid_quantity`]. + pub quantity: Option, + + /// A payer-provided note which will be seen by the recipient and reflected back in the invoice + /// response. Truncated to [`PAYER_NOTE_LIMIT`] characters. + pub payer_note_truncated: Option, +} + +/// The maximum number of characters included in [`InvoiceRequestFields::payer_note_truncated`]. +pub const PAYER_NOTE_LIMIT: usize = 512; + +impl Writeable for InvoiceRequestFields { + fn write(&self, writer: &mut W) -> Result<(), io::Error> { + write_tlv_fields!(writer, { + (0, self.payer_id, required), + (2, self.quantity.map(|v| HighZeroBytesDroppedBigSize(v)), option), + (4, self.payer_note_truncated.as_ref().map(|s| WithoutLength(&s.0)), option), + }); + Ok(()) + } +} + +impl Readable for InvoiceRequestFields { + fn read(reader: &mut R) -> Result { + _init_and_read_len_prefixed_tlv_fields!(reader, { + (0, payer_id, required), + (2, quantity, (option, encoding: (u64, HighZeroBytesDroppedBigSize))), + (4, payer_note_truncated, (option, encoding: (String, WithoutLength))), + }); + + Ok(InvoiceRequestFields { + payer_id: payer_id.0.unwrap(), + quantity, + payer_note_truncated: payer_note_truncated.map(|s| UntrustedString(s)), + }) + } +} + #[cfg(test)] mod tests { - use super::{InvoiceRequest, InvoiceRequestTlvStreamRef, SIGNATURE_TAG, UnsignedInvoiceRequest}; + use super::{InvoiceRequest, InvoiceRequestFields, InvoiceRequestTlvStreamRef, PAYER_NOTE_LIMIT, SIGNATURE_TAG, UnsignedInvoiceRequest}; use bitcoin::blockdata::constants::ChainHash; - use bitcoin::network::constants::Network; - use bitcoin::secp256k1::{KeyPair, Secp256k1, SecretKey, self}; - use core::convert::{Infallible, TryFrom}; + use bitcoin::network::Network; + use bitcoin::secp256k1::{Keypair, Secp256k1, SecretKey, self}; use core::num::NonZeroU64; #[cfg(feature = "std")] use core::time::Duration; @@ -1089,12 +1228,12 @@ mod tests { use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError}; use crate::offers::payer::PayerTlvStreamRef; use crate::offers::test_utils::*; - use crate::util::ser::{BigSize, Writeable}; - use crate::util::string::PrintableString; + use crate::util::ser::{BigSize, Readable, Writeable}; + use crate::util::string::{PrintableString, UntrustedString}; #[test] fn builds_invoice_request_with_defaults() { - let unsigned_invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + let unsigned_invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() @@ -1109,14 +1248,14 @@ mod tests { assert_eq!(unsigned_invoice_request.payer_metadata(), &[1; 32]); assert_eq!(unsigned_invoice_request.chains(), vec![ChainHash::using_genesis_block(Network::Bitcoin)]); assert_eq!(unsigned_invoice_request.metadata(), None); - assert_eq!(unsigned_invoice_request.amount(), Some(&Amount::Bitcoin { amount_msats: 1000 })); - assert_eq!(unsigned_invoice_request.description(), PrintableString("foo")); + assert_eq!(unsigned_invoice_request.amount(), Some(Amount::Bitcoin { amount_msats: 1000 })); + assert_eq!(unsigned_invoice_request.description(), Some(PrintableString(""))); assert_eq!(unsigned_invoice_request.offer_features(), &OfferFeatures::empty()); assert_eq!(unsigned_invoice_request.absolute_expiry(), None); assert_eq!(unsigned_invoice_request.paths(), &[]); assert_eq!(unsigned_invoice_request.issuer(), None); assert_eq!(unsigned_invoice_request.supported_quantity(), Quantity::One); - assert_eq!(unsigned_invoice_request.signing_pubkey(), recipient_pubkey()); + assert_eq!(unsigned_invoice_request.signing_pubkey(), Some(recipient_pubkey())); assert_eq!(unsigned_invoice_request.chain(), ChainHash::using_genesis_block(Network::Bitcoin)); assert_eq!(unsigned_invoice_request.amount_msats(), None); assert_eq!(unsigned_invoice_request.invoice_request_features(), &InvoiceRequestFeatures::empty()); @@ -1141,14 +1280,14 @@ mod tests { assert_eq!(invoice_request.payer_metadata(), &[1; 32]); assert_eq!(invoice_request.chains(), vec![ChainHash::using_genesis_block(Network::Bitcoin)]); assert_eq!(invoice_request.metadata(), None); - assert_eq!(invoice_request.amount(), Some(&Amount::Bitcoin { amount_msats: 1000 })); - assert_eq!(invoice_request.description(), PrintableString("foo")); + assert_eq!(invoice_request.amount(), Some(Amount::Bitcoin { amount_msats: 1000 })); + assert_eq!(invoice_request.description(), Some(PrintableString(""))); assert_eq!(invoice_request.offer_features(), &OfferFeatures::empty()); assert_eq!(invoice_request.absolute_expiry(), None); assert_eq!(invoice_request.paths(), &[]); assert_eq!(invoice_request.issuer(), None); assert_eq!(invoice_request.supported_quantity(), Quantity::One); - assert_eq!(invoice_request.signing_pubkey(), recipient_pubkey()); + assert_eq!(invoice_request.signing_pubkey(), Some(recipient_pubkey())); assert_eq!(invoice_request.chain(), ChainHash::using_genesis_block(Network::Bitcoin)); assert_eq!(invoice_request.amount_msats(), None); assert_eq!(invoice_request.invoice_request_features(), &InvoiceRequestFeatures::empty()); @@ -1156,7 +1295,7 @@ mod tests { assert_eq!(invoice_request.payer_id(), payer_pubkey()); assert_eq!(invoice_request.payer_note(), None); - let message = TaggedHash::new(SIGNATURE_TAG, &invoice_request.bytes); + let message = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &invoice_request.bytes); assert!(merkle::verify_signature(&invoice_request.signature, &message, payer_pubkey()).is_ok()); assert_eq!( @@ -1168,7 +1307,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, @@ -1183,6 +1322,7 @@ mod tests { quantity: None, payer_id: Some(&payer_pubkey()), payer_note: None, + paths: None, }, SignatureTlvStreamRef { signature: Some(&invoice_request.signature()) }, ), @@ -1199,7 +1339,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() @@ -1209,7 +1349,7 @@ mod tests { panic!("error building invoice_request: {:?}", e); } - match OfferBuilder::new("foo".into(), recipient_pubkey()) + match OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .absolute_expiry(past_expiry) .build().unwrap() @@ -1229,7 +1369,7 @@ mod tests { let secp_ctx = Secp256k1::new(); let payment_id = PaymentId([1; 32]); - let offer = OfferBuilder::new("foo".into(), recipient_pubkey()) + let offer = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap(); let invoice_request = offer @@ -1261,7 +1401,7 @@ mod tests { let mut bytes = Vec::new(); tlv_stream.write(&mut bytes).unwrap(); - let message = TaggedHash::new(INVOICE_SIGNATURE_TAG, &bytes); + let message = TaggedHash::from_valid_tlv_stream_bytes(INVOICE_SIGNATURE_TAG, &bytes); let signature = merkle::sign_message(recipient_sign, &message, recipient_pubkey()).unwrap(); signature_tlv_stream.signature = Some(&signature); @@ -1284,7 +1424,7 @@ mod tests { let mut bytes = Vec::new(); tlv_stream.write(&mut bytes).unwrap(); - let message = TaggedHash::new(INVOICE_SIGNATURE_TAG, &bytes); + let message = TaggedHash::from_valid_tlv_stream_bytes(INVOICE_SIGNATURE_TAG, &bytes); let signature = merkle::sign_message(recipient_sign, &message, recipient_pubkey()).unwrap(); signature_tlv_stream.signature = Some(&signature); @@ -1302,7 +1442,7 @@ mod tests { let secp_ctx = Secp256k1::new(); let payment_id = PaymentId([1; 32]); - let offer = OfferBuilder::new("foo".into(), recipient_pubkey()) + let offer = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap(); let invoice_request = offer @@ -1333,7 +1473,7 @@ mod tests { let mut bytes = Vec::new(); tlv_stream.write(&mut bytes).unwrap(); - let message = TaggedHash::new(INVOICE_SIGNATURE_TAG, &bytes); + let message = TaggedHash::from_valid_tlv_stream_bytes(INVOICE_SIGNATURE_TAG, &bytes); let signature = merkle::sign_message(recipient_sign, &message, recipient_pubkey()).unwrap(); signature_tlv_stream.signature = Some(&signature); @@ -1356,7 +1496,7 @@ mod tests { let mut bytes = Vec::new(); tlv_stream.write(&mut bytes).unwrap(); - let message = TaggedHash::new(INVOICE_SIGNATURE_TAG, &bytes); + let message = TaggedHash::from_valid_tlv_stream_bytes(INVOICE_SIGNATURE_TAG, &bytes); let signature = merkle::sign_message(recipient_sign, &message, recipient_pubkey()).unwrap(); signature_tlv_stream.signature = Some(&signature); @@ -1372,7 +1512,7 @@ mod tests { let mainnet = ChainHash::using_genesis_block(Network::Bitcoin); let testnet = ChainHash::using_genesis_block(Network::Testnet); - let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() @@ -1383,7 +1523,7 @@ mod tests { assert_eq!(invoice_request.chain(), mainnet); assert_eq!(tlv_stream.chain, None); - let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .chain(Network::Testnet) .build().unwrap() @@ -1395,7 +1535,7 @@ mod tests { assert_eq!(invoice_request.chain(), testnet); assert_eq!(tlv_stream.chain, Some(&testnet)); - let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .chain(Network::Bitcoin) .chain(Network::Testnet) @@ -1408,7 +1548,7 @@ mod tests { assert_eq!(invoice_request.chain(), mainnet); assert_eq!(tlv_stream.chain, None); - let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .chain(Network::Bitcoin) .chain(Network::Testnet) @@ -1422,7 +1562,7 @@ mod tests { assert_eq!(invoice_request.chain(), testnet); assert_eq!(tlv_stream.chain, Some(&testnet)); - match OfferBuilder::new("foo".into(), recipient_pubkey()) + match OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .chain(Network::Testnet) .build().unwrap() @@ -1433,7 +1573,7 @@ mod tests { Err(e) => assert_eq!(e, Bolt12SemanticError::UnsupportedChain), } - match OfferBuilder::new("foo".into(), recipient_pubkey()) + match OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .chain(Network::Testnet) .build().unwrap() @@ -1447,7 +1587,7 @@ mod tests { #[test] fn builds_invoice_request_with_amount() { - let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() @@ -1458,7 +1598,7 @@ mod tests { assert_eq!(invoice_request.amount_msats(), Some(1000)); assert_eq!(tlv_stream.amount, Some(1000)); - let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() @@ -1470,7 +1610,7 @@ mod tests { assert_eq!(invoice_request.amount_msats(), Some(1000)); assert_eq!(tlv_stream.amount, Some(1000)); - let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() @@ -1481,7 +1621,7 @@ mod tests { assert_eq!(invoice_request.amount_msats(), Some(1001)); assert_eq!(tlv_stream.amount, Some(1001)); - 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() @@ -1491,7 +1631,7 @@ mod tests { Err(e) => assert_eq!(e, Bolt12SemanticError::InsufficientAmount), } - match OfferBuilder::new("foo".into(), recipient_pubkey()) + match OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .supported_quantity(Quantity::Unbounded) .build().unwrap() @@ -1503,7 +1643,7 @@ mod tests { Err(e) => assert_eq!(e, Bolt12SemanticError::InsufficientAmount), } - 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() @@ -1513,7 +1653,7 @@ mod tests { Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidAmount), } - match OfferBuilder::new("foo".into(), recipient_pubkey()) + match OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .supported_quantity(Quantity::Unbounded) .build().unwrap() @@ -1526,7 +1666,7 @@ mod tests { Err(e) => assert_eq!(e, Bolt12SemanticError::InsufficientAmount), } - match OfferBuilder::new("foo".into(), recipient_pubkey()) + match OfferBuilder::new(recipient_pubkey()) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() .build() @@ -1535,7 +1675,7 @@ mod tests { Err(e) => assert_eq!(e, Bolt12SemanticError::MissingAmount), } - match OfferBuilder::new("foo".into(), recipient_pubkey()) + match OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .supported_quantity(Quantity::Unbounded) .build().unwrap() @@ -1550,7 +1690,7 @@ mod tests { #[test] fn builds_invoice_request_with_features() { - let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() @@ -1561,7 +1701,7 @@ mod tests { assert_eq!(invoice_request.invoice_request_features(), &InvoiceRequestFeatures::unknown()); assert_eq!(tlv_stream.features, Some(&InvoiceRequestFeatures::unknown())); - let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() @@ -1579,7 +1719,7 @@ mod tests { let one = NonZeroU64::new(1).unwrap(); let ten = NonZeroU64::new(10).unwrap(); - let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .supported_quantity(Quantity::One) .build().unwrap() @@ -1590,7 +1730,7 @@ mod tests { assert_eq!(invoice_request.quantity(), None); assert_eq!(tlv_stream.quantity, None); - match OfferBuilder::new("foo".into(), recipient_pubkey()) + match OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .supported_quantity(Quantity::One) .build().unwrap() @@ -1602,7 +1742,7 @@ mod tests { Err(e) => assert_eq!(e, Bolt12SemanticError::UnexpectedQuantity), } - let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .supported_quantity(Quantity::Bounded(ten)) .build().unwrap() @@ -1615,7 +1755,7 @@ mod tests { assert_eq!(invoice_request.amount_msats(), Some(10_000)); assert_eq!(tlv_stream.amount, Some(10_000)); - match OfferBuilder::new("foo".into(), recipient_pubkey()) + match OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .supported_quantity(Quantity::Bounded(ten)) .build().unwrap() @@ -1627,7 +1767,7 @@ mod tests { Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidQuantity), } - let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .supported_quantity(Quantity::Unbounded) .build().unwrap() @@ -1640,7 +1780,7 @@ mod tests { assert_eq!(invoice_request.amount_msats(), Some(2_000)); assert_eq!(tlv_stream.amount, Some(2_000)); - match OfferBuilder::new("foo".into(), recipient_pubkey()) + match OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .supported_quantity(Quantity::Unbounded) .build().unwrap() @@ -1651,7 +1791,7 @@ mod tests { Err(e) => assert_eq!(e, Bolt12SemanticError::MissingQuantity), } - match OfferBuilder::new("foo".into(), recipient_pubkey()) + match OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .supported_quantity(Quantity::Bounded(one)) .build().unwrap() @@ -1665,7 +1805,7 @@ mod tests { #[test] fn builds_invoice_request_with_payer_note() { - let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() @@ -1676,7 +1816,7 @@ mod tests { assert_eq!(invoice_request.payer_note(), Some(PrintableString("bar"))); assert_eq!(tlv_stream.payer_note, Some(&String::from("bar"))); - let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() @@ -1691,18 +1831,18 @@ mod tests { #[test] fn fails_signing_invoice_request() { - 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() .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() @@ -1716,7 +1856,7 @@ mod tests { #[test] fn fails_responding_with_unknown_required_features() { - match OfferBuilder::new("foo".into(), recipient_pubkey()) + match OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() .request_invoice(vec![42; 32], payer_pubkey()).unwrap() @@ -1732,7 +1872,7 @@ mod tests { #[test] fn parses_invoice_request_with_metadata() { - let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() @@ -1749,7 +1889,7 @@ mod tests { #[test] fn parses_invoice_request_with_chain() { - let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() @@ -1764,7 +1904,7 @@ mod tests { panic!("error parsing invoice_request: {:?}", e); } - let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() @@ -1783,7 +1923,7 @@ mod tests { #[test] fn parses_invoice_request_with_amount() { - let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() @@ -1797,7 +1937,7 @@ mod tests { panic!("error parsing invoice_request: {:?}", e); } - let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice_request = OfferBuilder::new(recipient_pubkey()) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() .amount_msats(1000).unwrap() @@ -1811,7 +1951,7 @@ mod tests { panic!("error parsing invoice_request: {:?}", e); } - let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice_request = OfferBuilder::new(recipient_pubkey()) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() .build_unchecked() @@ -1825,7 +1965,7 @@ mod tests { Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingAmount)), } - let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() @@ -1841,7 +1981,8 @@ mod tests { Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::InsufficientAmount)), } - let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice_request = OfferBuilder::new(recipient_pubkey()) + .description("foo".to_string()) .amount(Amount::Currency { iso4217_code: *b"USD", amount: 1000 }) .build_unchecked() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() @@ -1858,7 +1999,7 @@ mod tests { }, } - let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .supported_quantity(Quantity::Unbounded) .build().unwrap() @@ -1881,7 +2022,7 @@ mod tests { let one = NonZeroU64::new(1).unwrap(); let ten = NonZeroU64::new(10).unwrap(); - let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .supported_quantity(Quantity::One) .build().unwrap() @@ -1896,7 +2037,7 @@ mod tests { panic!("error parsing invoice_request: {:?}", e); } - let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .supported_quantity(Quantity::One) .build().unwrap() @@ -1916,7 +2057,7 @@ mod tests { }, } - let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .supported_quantity(Quantity::Bounded(ten)) .build().unwrap() @@ -1933,7 +2074,7 @@ mod tests { panic!("error parsing invoice_request: {:?}", e); } - let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .supported_quantity(Quantity::Bounded(ten)) .build().unwrap() @@ -1951,7 +2092,7 @@ mod tests { Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::InvalidQuantity)), } - let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .supported_quantity(Quantity::Unbounded) .build().unwrap() @@ -1968,7 +2109,7 @@ mod tests { panic!("error parsing invoice_request: {:?}", e); } - let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .supported_quantity(Quantity::Unbounded) .build().unwrap() @@ -1984,7 +2125,7 @@ mod tests { Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingQuantity)), } - let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .supported_quantity(Quantity::Bounded(one)) .build().unwrap() @@ -2003,7 +2144,7 @@ mod tests { #[test] fn fails_parsing_invoice_request_without_metadata() { - let offer = OfferBuilder::new("foo".into(), recipient_pubkey()) + let offer = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap(); let unsigned_invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap() @@ -2024,7 +2165,7 @@ mod tests { #[test] fn fails_parsing_invoice_request_without_payer_id() { - let offer = OfferBuilder::new("foo".into(), recipient_pubkey()) + let offer = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap(); let unsigned_invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap() @@ -2043,7 +2184,7 @@ mod tests { #[test] fn fails_parsing_invoice_request_without_node_id() { - let offer = OfferBuilder::new("foo".into(), recipient_pubkey()) + let offer = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap(); let unsigned_invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap() @@ -2065,7 +2206,7 @@ mod tests { #[test] fn fails_parsing_invoice_request_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() @@ -2081,7 +2222,7 @@ mod tests { #[test] fn fails_parsing_invoice_request_with_invalid_signature() { - let mut invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey()) + let mut invoice_request = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() @@ -2104,14 +2245,14 @@ mod tests { #[test] fn fails_parsing_invoice_request_with_extra_tlv_records() { let secp_ctx = Secp256k1::new(); - let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap()); - let invoice_request = OfferBuilder::new("foo".into(), keys.public_key()) + let keys = Keypair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap()); + let invoice_request = OfferBuilder::new(keys.public_key()) .amount_msats(1000) .build().unwrap() .request_invoice(vec![1; 32], keys.public_key()).unwrap() .build().unwrap() - .sign::<_, Infallible>( - |message| Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) + .sign(|message: &UnsignedInvoiceRequest| + Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys)) ) .unwrap(); @@ -2126,4 +2267,51 @@ mod tests { Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::InvalidValue)), } } + + #[test] + fn copies_verified_invoice_request_fields() { + let node_id = recipient_pubkey(); + let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); + let entropy = FixedEntropy {}; + let secp_ctx = Secp256k1::new(); + + #[cfg(c_bindings)] + use crate::offers::offer::OfferWithDerivedMetadataBuilder as OfferBuilder; + let offer = OfferBuilder + ::deriving_signing_pubkey(node_id, &expanded_key, &entropy, &secp_ctx) + .chain(Network::Testnet) + .amount_msats(1000) + .supported_quantity(Quantity::Unbounded) + .build().unwrap(); + assert_eq!(offer.signing_pubkey(), Some(node_id)); + + let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .chain(Network::Testnet).unwrap() + .quantity(1).unwrap() + .payer_note("0".repeat(PAYER_NOTE_LIMIT * 2)) + .build().unwrap() + .sign(payer_sign).unwrap(); + match invoice_request.verify(&expanded_key, &secp_ctx) { + Ok(invoice_request) => { + let fields = invoice_request.fields(); + assert_eq!(invoice_request.offer_id, offer.id()); + assert_eq!( + fields, + InvoiceRequestFields { + payer_id: payer_pubkey(), + quantity: Some(1), + payer_note_truncated: Some(UntrustedString("0".repeat(PAYER_NOTE_LIMIT))), + } + ); + + let mut buffer = Vec::new(); + fields.write(&mut buffer).unwrap(); + + let deserialized_fields: InvoiceRequestFields = + Readable::read(&mut buffer.as_slice()).unwrap(); + assert_eq!(deserialized_fields, fields); + }, + Err(_) => panic!("unexpected error"), + } + } }