X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Foffers%2Finvoice_request.rs;h=aa6d7c067d0c8f54a2ce06b474038d5dd038a3ad;hb=e61001f60daa4f3d96c3b3f54cd3a8b00051ebe0;hp=f282075d9333cf1f9002154519474518036b9336;hpb=9277166a8b10bc0eff18fadc8fcff8a4a6a5673e;p=rust-lightning diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index f282075d..aa6d7c06 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -61,7 +61,6 @@ use bitcoin::blockdata::constants::ChainHash; use bitcoin::network::constants::Network; use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, self}; use bitcoin::secp256k1::schnorr::Signature; -use core::convert::{AsRef, TryFrom}; use core::ops::Deref; use crate::sign::EntropySource; use crate::io; @@ -73,12 +72,12 @@ use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce}; use crate::ln::msgs::DecodeError; use crate::offers::invoice::BlindedPayInfo; use crate::offers::merkle::{SignError, SignFn, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, self}; -use crate::offers::offer::{Offer, OfferContents, OfferTlvStream, OfferTlvStreamRef}; +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 { @@ -89,6 +88,7 @@ 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. @@ -529,7 +529,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 } } @@ -607,6 +607,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, @@ -744,7 +747,27 @@ macro_rules! invoice_request_respond_with_explicit_signing_pubkey_methods { ( return Err(Bolt12SemanticError::UnknownRequiredFeatures); } - <$builder>::for_offer(&$contents, 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) } } } @@ -764,8 +787,9 @@ macro_rules! invoice_request_verify_method { ($self: ident, $self_type: ty) => { #[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 { + offer_id, #[cfg(not(c_bindings))] inner: $self, #[cfg(c_bindings)] @@ -851,6 +875,11 @@ macro_rules! invoice_request_respond_with_derived_signing_pubkey_methods { ( Some(keys) => 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 ) @@ -868,6 +897,24 @@ impl VerifiedInvoiceRequest { 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, + amount_msats: *amount_msats, + features: features.clone(), + quantity: *quantity, + payer_note_truncated: payer_note.clone() + .map(|mut s| { s.truncate(PAYER_NOTE_LIMIT); UntrustedString(s) }), + } + } } impl InvoiceRequestContents { @@ -939,6 +986,7 @@ impl InvoiceRequestContentsWithoutPayerId { quantity: self.quantity, payer_id: None, payer_note: self.payer_note.as_ref(), + paths: None, }; (payer, offer, invoice_request) @@ -971,6 +1019,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)), @@ -978,6 +1028,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 = @@ -1022,7 +1074,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 }) } @@ -1046,7 +1098,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 }) @@ -1060,7 +1112,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 { @@ -1087,6 +1141,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, @@ -1096,14 +1154,72 @@ 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 amount to pay in msats (i.e., the minimum lightning-payable unit for [`chain`]), which + /// must be greater than or equal to [`Offer::amount`], converted if necessary. + /// + /// [`chain`]: InvoiceRequest::chain + pub amount_msats: Option, + + /// Features pertaining to requesting an invoice. + pub features: InvoiceRequestFeatures, + + /// 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.amount_msats.map(|v| HighZeroBytesDroppedBigSize(v)), option), + (4, WithoutLength(&self.features), required), + (6, self.quantity.map(|v| HighZeroBytesDroppedBigSize(v)), option), + (8, 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, amount_msats, (option, encoding: (u64, HighZeroBytesDroppedBigSize))), + (4, features, (option, encoding: (InvoiceRequestFeatures, WithoutLength))), + (6, quantity, (option, encoding: (u64, HighZeroBytesDroppedBigSize))), + (8, payer_note_truncated, (option, encoding: (String, WithoutLength))), + }); + let features = features.unwrap_or(InvoiceRequestFeatures::empty()); + + Ok(InvoiceRequestFields { + payer_id: payer_id.0.unwrap(), amount_msats, features, 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::TryFrom; use core::num::NonZeroU64; #[cfg(feature = "std")] use core::time::Duration; @@ -1126,12 +1242,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() @@ -1147,13 +1263,13 @@ mod tests { 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.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()); @@ -1179,13 +1295,13 @@ mod tests { 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.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()); @@ -1193,7 +1309,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!( @@ -1205,7 +1321,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, @@ -1220,6 +1336,7 @@ mod tests { quantity: None, payer_id: Some(&payer_pubkey()), payer_note: None, + paths: None, }, SignatureTlvStreamRef { signature: Some(&invoice_request.signature()) }, ), @@ -1236,7 +1353,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() @@ -1246,7 +1363,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() @@ -1266,7 +1383,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 @@ -1298,7 +1415,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); @@ -1321,7 +1438,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); @@ -1339,7 +1456,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 @@ -1370,7 +1487,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); @@ -1393,7 +1510,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); @@ -1409,7 +1526,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() @@ -1420,7 +1537,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() @@ -1432,7 +1549,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) @@ -1445,7 +1562,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) @@ -1459,7 +1576,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() @@ -1470,7 +1587,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() @@ -1484,7 +1601,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() @@ -1495,7 +1612,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() @@ -1507,7 +1624,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() @@ -1518,7 +1635,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() @@ -1528,7 +1645,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() @@ -1540,7 +1657,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() @@ -1550,7 +1667,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() @@ -1563,7 +1680,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() @@ -1572,7 +1689,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() @@ -1587,7 +1704,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() @@ -1598,7 +1715,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() @@ -1616,7 +1733,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() @@ -1627,7 +1744,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() @@ -1639,7 +1756,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() @@ -1652,7 +1769,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() @@ -1664,7 +1781,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() @@ -1677,7 +1794,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() @@ -1688,7 +1805,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() @@ -1702,7 +1819,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() @@ -1713,7 +1830,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() @@ -1728,7 +1845,7 @@ 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() @@ -1739,7 +1856,7 @@ mod tests { 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() @@ -1753,7 +1870,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() @@ -1769,7 +1886,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() @@ -1786,7 +1903,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() @@ -1801,7 +1918,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() @@ -1820,7 +1937,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() @@ -1834,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() .amount_msats(1000).unwrap() @@ -1848,7 +1965,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() @@ -1862,7 +1979,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() @@ -1878,7 +1995,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() @@ -1895,7 +2013,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() @@ -1918,7 +2036,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() @@ -1933,7 +2051,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() @@ -1953,7 +2071,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() @@ -1970,7 +2088,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() @@ -1988,7 +2106,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() @@ -2005,7 +2123,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() @@ -2021,7 +2139,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() @@ -2040,7 +2158,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() @@ -2061,7 +2179,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() @@ -2080,7 +2198,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() @@ -2102,7 +2220,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() @@ -2118,7 +2236,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() @@ -2142,7 +2260,7 @@ mod tests { 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 invoice_request = OfferBuilder::new(keys.public_key()) .amount_msats(1000) .build().unwrap() .request_invoice(vec![1; 32], keys.public_key()).unwrap() @@ -2163,4 +2281,54 @@ 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() + .amount_msats(1001).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(), + amount_msats: Some(1001), + features: InvoiceRequestFeatures::empty(), + 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"), + } + } }