X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Foffers%2Finvoice.rs;h=5bd5a95d8e627a3e69964db540fbffa554bbddf4;hb=0456b0e311a4996b00d715f9211adcabc06b37c7;hp=75a844cd117abe5d956ddcbdf7efde5bff44a769;hpb=3dffe54258a374d15571d4ec72f5faa02477b770;p=rust-lightning diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index 75a844cd..5bd5a95d 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -110,6 +110,7 @@ use core::time::Duration; use crate::io; use crate::blinded_path::BlindedPath; use crate::ln::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; @@ -128,7 +129,7 @@ use crate::prelude::*; #[cfg(feature = "std")] use std::time::SystemTime; -const DEFAULT_RELATIVE_EXPIRY: Duration = Duration::from_secs(7200); +pub(crate) const DEFAULT_RELATIVE_EXPIRY: Duration = Duration::from_secs(7200); /// Tag for the hash function used when signing a [`Bolt12Invoice`]'s merkle root. pub const SIGNATURE_TAG: &'static str = concat!("lightning", "invoice", "signature"); @@ -173,7 +174,7 @@ impl<'a> InvoiceBuilder<'a, ExplicitSigningPubkey> { invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, created_at: Duration, payment_hash: PaymentHash ) -> Result { - let amount_msats = Self::check_amount_msats(invoice_request)?; + 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(), @@ -206,7 +207,7 @@ impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> { invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, created_at: Duration, payment_hash: PaymentHash, keys: KeyPair ) -> Result { - let amount_msats = Self::check_amount_msats(invoice_request)?; + 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(), @@ -236,7 +237,9 @@ impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> { } impl<'a, S: SigningPubkeyStrategy> InvoiceBuilder<'a, S> { - fn check_amount_msats(invoice_request: &InvoiceRequest) -> Result { + pub(crate) fn amount_msats( + invoice_request: &InvoiceRequest + ) -> Result { match invoice_request.amount_msats() { Some(amount_msats) => Ok(amount_msats), None => match invoice_request.contents.inner.offer.amount() { @@ -338,6 +341,12 @@ impl<'a> InvoiceBuilder<'a, ExplicitSigningPubkey> { } } + #[cfg(not(feature = "std"))] { + if self.invoice.is_offer_or_refund_expired_no_std(self.invoice.created_at()) { + return Err(Bolt12SemanticError::AlreadyExpired); + } + } + let InvoiceBuilder { invreq_bytes, invoice, .. } = self; Ok(UnsignedBolt12Invoice::new(invreq_bytes, invoice)) } @@ -354,6 +363,12 @@ impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> { } } + #[cfg(not(feature = "std"))] { + if self.invoice.is_offer_or_refund_expired_no_std(self.invoice.created_at()) { + return Err(Bolt12SemanticError::AlreadyExpired); + } + } + let InvoiceBuilder { invreq_bytes, invoice, signing_pubkey_strategy: DerivedSigningPubkey(keys) } = self; @@ -424,6 +439,7 @@ impl UnsignedBolt12Invoice { bytes: self.bytes, contents: self.contents, signature, + tagged_hash: self.tagged_hash, }) } } @@ -448,6 +464,7 @@ pub struct Bolt12Invoice { bytes: Vec, contents: InvoiceContents, signature: Signature, + tagged_hash: TaggedHash, } /// The contents of an [`Bolt12Invoice`] for responding to either an [`Offer`] or a [`Refund`]. @@ -692,13 +709,14 @@ impl Bolt12Invoice { /// Hash that was used for signing the invoice. pub fn signable_hash(&self) -> [u8; 32] { - merkle::message_digest(SIGNATURE_TAG, &self.bytes).as_ref().clone() + self.tagged_hash.as_digest().as_ref().clone() } - /// Verifies that the invoice was for a request or refund created using the given key. + /// Verifies that the invoice was for a request or refund created using the given key. Returns + /// the associated [`PaymentId`] to use when sending the payment. pub fn verify( &self, key: &ExpandedKey, secp_ctx: &Secp256k1 - ) -> bool { + ) -> Result { self.contents.verify(TlvStream::new(&self.bytes), key, secp_ctx) } @@ -725,6 +743,16 @@ impl InvoiceContents { } } + #[cfg(not(feature = "std"))] + fn is_offer_or_refund_expired_no_std(&self, duration_since_epoch: Duration) -> bool { + match self { + InvoiceContents::ForOffer { invoice_request, .. } => + invoice_request.inner.offer.is_expired_no_std(duration_since_epoch), + InvoiceContents::ForRefund { refund, .. } => + refund.is_expired_no_std(duration_since_epoch), + } + } + fn offer_chains(&self) -> Option> { match self { InvoiceContents::ForOffer { invoice_request, .. } => @@ -947,7 +975,7 @@ impl InvoiceContents { fn verify( &self, tlv_stream: TlvStream<'_>, key: &ExpandedKey, secp_ctx: &Secp256k1 - ) -> bool { + ) -> Result { let offer_records = tlv_stream.clone().range(OFFER_TYPES); let invreq_records = tlv_stream.range(INVOICE_REQUEST_TYPES).filter(|record| { match record.r#type { @@ -967,10 +995,7 @@ impl InvoiceContents { }, }; - match signer::verify_metadata(metadata, key, iv_bytes, payer_id, tlv_stream, secp_ctx) { - Ok(_) => true, - Err(()) => false, - } + signer::verify_payer_metadata(metadata, key, iv_bytes, payer_id, tlv_stream, secp_ctx) } fn derives_keys(&self) -> bool { @@ -1189,11 +1214,11 @@ impl TryFrom> for Bolt12Invoice { None => return Err(Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingSignature)), Some(signature) => signature, }; - let message = TaggedHash::new(SIGNATURE_TAG, &bytes); + let tagged_hash = TaggedHash::new(SIGNATURE_TAG, &bytes); let pubkey = contents.fields().signing_pubkey; - merkle::verify_signature(&signature, message, pubkey)?; + merkle::verify_signature(&signature, &tagged_hash, pubkey)?; - Ok(Bolt12Invoice { bytes, contents, signature }) + Ok(Bolt12Invoice { bytes, contents, signature, tagged_hash }) } } @@ -1408,7 +1433,7 @@ mod tests { assert_eq!(invoice.signing_pubkey(), recipient_pubkey()); let message = TaggedHash::new(SIGNATURE_TAG, &invoice.bytes); - assert!(merkle::verify_signature(&invoice.signature, message, recipient_pubkey()).is_ok()); + assert!(merkle::verify_signature(&invoice.signature, &message, recipient_pubkey()).is_ok()); let digest = Message::from_slice(&invoice.signable_hash()).unwrap(); let pubkey = recipient_pubkey().into(); @@ -1505,7 +1530,7 @@ mod tests { assert_eq!(invoice.signing_pubkey(), recipient_pubkey()); let message = TaggedHash::new(SIGNATURE_TAG, &invoice.bytes); - assert!(merkle::verify_signature(&invoice.signature, message, recipient_pubkey()).is_ok()); + assert!(merkle::verify_signature(&invoice.signature, &message, recipient_pubkey()).is_ok()); assert_eq!( invoice.as_tlv_stream(), @@ -1642,36 +1667,31 @@ mod tests { .build().unwrap() .sign(payer_sign).unwrap(); - if let Err(e) = invoice_request - .verify_and_respond_using_derived_keys_no_std( - payment_paths(), payment_hash(), now(), &expanded_key, &secp_ctx - ) - .unwrap() + if let Err(e) = invoice_request.clone() + .verify(&expanded_key, &secp_ctx).unwrap() + .respond_using_derived_keys_no_std(payment_paths(), payment_hash(), now()).unwrap() .build_and_sign(&secp_ctx) { panic!("error building invoice: {:?}", e); } let expanded_key = ExpandedKey::new(&KeyMaterial([41; 32])); - match invoice_request.verify_and_respond_using_derived_keys_no_std( - payment_paths(), payment_hash(), now(), &expanded_key, &secp_ctx - ) { - Ok(_) => panic!("expected error"), - Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidMetadata), - } + 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) .amount_msats(1000) + // Omit the path so that node_id is used for the signing pubkey instead of deriving .build().unwrap(); let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap() .build().unwrap() .sign(payer_sign).unwrap(); - match invoice_request.verify_and_respond_using_derived_keys_no_std( - payment_paths(), payment_hash(), now(), &expanded_key, &secp_ctx - ) { + match invoice_request + .verify(&expanded_key, &secp_ctx).unwrap() + .respond_using_derived_keys_no_std(payment_paths(), payment_hash(), now()) + { Ok(_) => panic!("expected error"), Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidMetadata), }